Add existing PDF form authoring and widget actions - #2266
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7eed52d001
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d51d1b35aa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ((options.Kind == PdfFormFieldCreationKind.RadioButtonGroup || | ||
| options.Kind == PdfFormFieldCreationKind.Choice && options.ChoiceOptions.Count > 0) && | ||
| string.IsNullOrEmpty(options.Value)) { | ||
| return options.ChoiceOptions[0]; |
There was a problem hiding this comment.
Preserve empty initial choice values
When a caller supplies nonempty ChoiceOptions but leaves Value at its documented empty default, this branch substitutes the first option. The validation explicitly accepts empty choice values, and the previous creation path wrote options.Value directly, so list and combo fields can no longer be authored in an initially unselected state. Restrict this fallback to radio groups or otherwise distinguish an unspecified radio value.
Useful? React with 👍 / 👎.
| if (!string.IsNullOrEmpty(options.Value) && !isEditableChoice && !options.ChoiceOptions.Contains(options.Value, StringComparer.Ordinal)) throw new ArgumentException("Choice value must match one of the provided options.", nameof(options)); | ||
| if (options.DefaultValue is not null && !isEditableChoice && !options.ChoiceOptions.Contains(options.DefaultValue, StringComparer.Ordinal)) throw new ArgumentException("Choice default value must match one of the provided options.", nameof(options)); | ||
| } | ||
| if (options.Style?.IsComb == true && !options.Style.MaxLength.HasValue) throw new ArgumentException("Comb text fields require a maximum length.", nameof(options)); |
There was a problem hiding this comment.
Reject incompatible comb styles
When a text field sets IsComb and MaxLength together with IsMultiline, IsPassword, or IsFileSelect, this validation passes and GetCreateFieldFlags emits the incompatible flags. The existing writer rejects exactly these combinations in PdfAnnotationDictionaryBuilder.ValidateCombTextFieldStyle, while the appearance builder may render the field as multiline instead of comb, leaving an invalid or ambiguous AcroForm field. Apply the same exclusions in this authoring path.
Useful? React with 👍 / 👎.
| TryReadInteger(dictionary, "F"), | ||
| ReadWidgetNormalAppearanceStates(dictionary)); | ||
| ReadWidgetNormalAppearanceStates(dictionary), | ||
| ReadWidgetActions(dictionary, actionBudget)); |
There was a problem hiding this comment.
Budget actions before rejecting widget geometry
For a malformed or geometry-less widget whose /Rect is absent or unreadable, the earlier guard returns before this action reader is invoked. Any /A, /AA, or chained JavaScript on that widget is therefore omitted from the typed action inventory and from FormWidgetJavaScriptCount/FormWidgetJavaScriptBytes, allowing existing scripts to bypass the configured widget-action budgets when a subsequent form edit adds more JavaScript. Scan and budget actions independently of whether the widget geometry can be exposed.
Useful? React with 👍 / 👎.
| if (options.Kind == PdfFormFieldCreationKind.Text && options.Style?.IsMultiline == true && !field.IsMultiline) throw new InvalidOperationException("AcroForm multiline text-field readback validation failed for " + options.Name + "."); | ||
| if (options.Kind == PdfFormFieldCreationKind.Choice && field.IsCombo != IsChoiceComboBox(options)) throw new InvalidOperationException("AcroForm choice presentation readback validation failed for " + options.Name + "."); | ||
| if (options.Kind == PdfFormFieldCreationKind.RadioButtonGroup && (!field.IsRadioButton || field.WidgetCount != options.ChoiceOptions.Count)) throw new InvalidOperationException("AcroForm radio-button readback validation failed for " + options.Name + "."); | ||
| if (options.Kind == PdfFormFieldCreationKind.PushButton && !field.IsPushButton) throw new InvalidOperationException("AcroForm push-button readback validation failed for " + options.Name + "."); |
There was a problem hiding this comment.
Account for later flag edits during create readback
When one transaction creates a choice field and then calls SetFlags to change its combo bit, the saved field legitimately reflects the later command, but this check still compares it with the original create options and throws before the successful result can be returned. The same conflict occurs when later flags clear an authored multiline, radio, or push-button bit. Evaluate create assertions against the final command state, or skip flag-derived create checks when a later SetFlags targets the field.
Useful? React with 👍 / 👎.
|
|
||
| return new PdfSanitizationResult(sanitized, plan, before, remaining, quarantined); | ||
| var preservationOptions = new PdfRewritePreservationOptions { | ||
| OriginalReadOptions = readOptions, |
There was a problem hiding this comment.
Reuse custom read limits for sanitized readback
When sanitization is invoked through a PdfDocument opened with raised semantic limits, only the original inspection receives those options; AssertPreserved inspects the rewritten artifact with default limits. For example, a source accepted with MaxFormFields above the default can be rewritten successfully and pass the preceding sanitizer scan, then fail here solely because preservation readback falls back to the default field limit. Supply corresponding RewrittenReadOptions, as the AcroForm editor does for its rewritten artifact.
Useful? React with 👍 / 👎.
| PreserveCatalogActions = false, | ||
| PreservePageActions = false, | ||
| PreserveOpenAction = false, |
There was a problem hiding this comment.
Prove preservation of allowlisted actions
When AllowedActionTypes retains an action such as JavaScript, that action is outside the sanitization removal policy, but these hardcoded settings disable preservation comparisons for catalog, page, and open actions unconditionally. Consequently PreservationReport.IsPreserved can remain true even if the rewrite accidentally drops an explicitly allowlisted action, contrary to the new property's claim to prove preservation outside the policy. Preserve or selectively compare action surfaces that the active policy allows.
Useful? React with 👍 / 👎.
| PdfFormFieldStyle style = options.Style?.Clone() ?? new PdfFormFieldStyle(); | ||
| style.TextAlignment = PdfFormFieldTextAlignment.Center; | ||
| PdfStream appearance = PdfFormFiller.CreateAuthoredTextWidgetAppearance( |
There was a problem hiding this comment.
Strip text-only flags from button appearance styles
When a style intended for reuse has a text-only option such as IsPassword set, this clone is passed unchanged into the text-field appearance builder for a push-button caption; GetTextFieldAppearanceDisplayValue then masks the caption with asterisks even though GetCreateFieldFlags correctly does not mark the push button as a password field. The labeled-radio path similarly clones the full style and can mask its option labels. Clear text-only behaviors such as password, multiline, and comb before rendering button captions and radio labels.
Useful? React with 👍 / 👎.
Summary
/Aor activation/AA /U, and expose full dictionary/array/Nextchains as inert typed read models without executing themExample
Validation
OfficeIMO.Pdf.Tests: 3,480/3,480 on .NET 8 and 3,480/3,480 on .NET 10OfficeIMO.Pdfnetstandard2.0 build: no warnings or errorsStack
This PR depends on #2265 for the shared safe JavaScript string encoding and document-action foundation. It is intentionally based on
feature/pdf-interactive-actions.