mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-12 01:11:19 +00:00
* AcroForm: integrate persistent timesheet script workflows * AcroForm: address review caching, worker stacks and live metadata * M6: address second review of AcroForm script workflows * M6: handle top-level ASI boundaries and verify cyclic calculation results * M6: bound script workers and isolate pending completions across edits * M6: make form resets visible before subsequent script reads * M6: include calculated dependents in reset comparison baselines * M6: bound reset writeback work before native completion * M6: charge expanded reset selections and field name bytes * M6: bound string conversion bytes and charge reset widgets * M6: preserve unread defaults and bound prompt text conversion * M6: bound reset calls early and preserve calculated field identity * AcroForm: address grouped calculations, field identity and widget ownership * AcroForm: retire field instances and address native review edge cases * AcroForm: route comparison Save As and Print to the source document * M6: dispatch text widget mouse-up scripts * M6: honor fit bounds for form button icons * AcroForm: bound calculation work and preserve reset and save integrity * Harden AcroForm script requests and worker recovery * Keep form callbacks and focus consistent across edits * AcroForm: preserve reset ordering and bound edit callbacks * Fix reset appearance ordering and XFA focus after row removal * Keep XFA focus indexes and stopped reset appearances consistent * Document form script execution and reset helpers * Keep script workers bounded and reject partial callback edits * Keep form events available after a document script reaches VM limits * Keep main's first-value Simple calculation and restore the calculateNow helper test AFSimple_Calculate (JavaScript) and the native Simple calculation count each field's first value again, as #5304 landed them; summing every selection of a multiple-selection list is a separate behaviour change. Restores the document-helper calculateNow test and the AFSimple_Calculate doc comment, and adds native and JavaScript multiple-selection cases. * Patch the callback field snapshot in place instead of rebuilding it Each field callback rebuilt all field states (3,129 on the benchmark form), and the retained realm held the previous snapshot, so stamping instance tokens copied it again. The runner now keeps its last snapshot and patches only the values and flags that changed; ScriptSession::stamp_fields stamps it before dispatch, the worker copies only when a token changes, and the realm drops the job's snapshot when the job ends (field identity is checked against the cached keys instead). * An oversized job script is that script's error, not a refused edit Realm::run refused a script too long or too deep to run as an execution refusal, which made the engine reject the whole edit (setting a 300 KiB calculation script failed). As on main it is now a plain script error that keeps the value; the realm is not touched. * Read a leading #! line as a comment in the chain scan; follow main's refusal and conditional limits in tests The nesting guard from #5079 now checks brackets, prefixes and conditionals; the document worker's preflight still bounds arrow-function and right-associative chains and skips a leading #! line as the guard does. A refused script is an error, not a refused edit, so the preflight test now checks the edit goes through with the error reported. --------- Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
143 lines
5.7 KiB
Rust
143 lines
5.7 KiB
Rust
//! Prepare a form: create, change and delete fields (execution plan M6.6).
|
|
//!
|
|
//! New fields follow Acrobat's conventions: names "Text1", "Check Box1", "Group1" (radio groups),
|
|
//! "Dropdown1", "List Box1", "Button1", "Signature1", "Date1"; Helvetica at Acrobat's Auto size
|
|
//! for text and date fields (12 pt in a box that has room, smaller in a short one), 12 pt in
|
|
//! choice fields, a thin grey border and a white background, and appearance streams generated
|
|
//! immediately so every viewer shows the empty field.
|
|
|
|
use pdfcraft_cos::{Dict, Document, ObjRef, Object, PdfString, Stream};
|
|
|
|
use crate::{Field, FieldKind, FormError, Widget, appearance, fields, flags, page_refs};
|
|
|
|
mod edit;
|
|
mod join;
|
|
mod lifecycle;
|
|
mod look;
|
|
mod tree;
|
|
|
|
pub(crate) use tree::ancestor_sets;
|
|
|
|
pub use edit::set_props;
|
|
pub(crate) use edit::set_props_object;
|
|
pub use lifecycle::{add_field, delete_field, delete_field_object, delete_widget, duplicate_field};
|
|
pub(crate) use look::button_appearance;
|
|
pub(crate) use look::redraw_field_object;
|
|
pub use look::{
|
|
BorderStyle, CheckStyle, FieldFont, Look, LookPatch, check_style, look, redraw_field, redraw_fields, set_button_icon, set_button_icon_target,
|
|
};
|
|
pub(crate) use tree::{ensure_form, form_fonts_as_objects};
|
|
|
|
/// The smallest width and height of a field's widget, in points: a smaller one is refused.
|
|
pub const MIN_FIELD_SIDE: f64 = 4.0;
|
|
|
|
/// The kind of field to add.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum NewField {
|
|
Text {
|
|
multiline: bool,
|
|
},
|
|
/// A date field: a text field with Acrobat's date format action (`AFDate_FormatEx`).
|
|
Date,
|
|
CheckBox,
|
|
/// One radio button in `group` (a new group is created when none of that name exists),
|
|
/// selected by `export`.
|
|
Radio {
|
|
group: Option<String>,
|
|
export: String,
|
|
},
|
|
Combo {
|
|
options: Vec<String>,
|
|
editable: bool,
|
|
},
|
|
List {
|
|
options: Vec<String>,
|
|
multi: bool,
|
|
},
|
|
Button {
|
|
caption: String,
|
|
},
|
|
/// An image field: an icon-only push button that asks for a picture when clicked.
|
|
Image,
|
|
Signature,
|
|
}
|
|
|
|
impl NewField {
|
|
fn base_name(&self) -> &'static str {
|
|
match self {
|
|
NewField::Text { .. } => "Text",
|
|
NewField::Date => "Date",
|
|
NewField::CheckBox => "Check Box",
|
|
NewField::Radio { .. } => "Group",
|
|
NewField::Combo { .. } => "Dropdown",
|
|
NewField::List { .. } => "List Box",
|
|
NewField::Button { .. } => "Button",
|
|
NewField::Image => "Image",
|
|
NewField::Signature => "Signature",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Field properties the Properties dialog edits (`None` leaves a value unchanged).
|
|
#[derive(Clone, Debug, Default, PartialEq)]
|
|
pub struct FieldProps {
|
|
pub name: Option<String>,
|
|
pub tooltip: Option<String>,
|
|
/// Normal push-button label (`/MK /CA`) of one widget (0-based index).
|
|
/// An empty string clears the label; other widgets and icon layouts are preserved.
|
|
pub caption: Option<(usize, String)>,
|
|
/// Normal push-button label for every widget in the field.
|
|
pub caption_all: Option<String>,
|
|
pub read_only: Option<bool>,
|
|
pub required: Option<bool>,
|
|
pub multiline: Option<bool>,
|
|
pub max_len: Option<Option<usize>>,
|
|
/// Choice options (display texts; export values equal them).
|
|
pub options: Option<Vec<String>>,
|
|
/// Change one button widget's export value without changing its appearance state.
|
|
/// Widgets sharing that state are kept in sync. The index is zero-based.
|
|
pub export_value: Option<(usize, String)>,
|
|
/// Font size (0 = auto).
|
|
pub font_size: Option<f64>,
|
|
/// Position: move or resize one widget (its index in [`Field::widgets`]) to a new rect in
|
|
/// user space.
|
|
pub rect: Option<(usize, [f64; 4])>,
|
|
/// Appearance tab (border, fill, line, text colour, font).
|
|
pub look: Option<Look>,
|
|
/// Change only these appearance properties. Unlike `look`, this preserves each widget's
|
|
/// other colours, border settings and default appearance, including custom font resources.
|
|
pub appearance: Option<LookPatch>,
|
|
/// Format, Validate and Calculate tabs (written as Acrobat's AF scripts).
|
|
pub format: Option<crate::af::Format>,
|
|
pub validate: Option<crate::af::Validate>,
|
|
pub calculate: Option<crate::af::Calculate>,
|
|
/// Options-tab flags (`/Ff` bits from [`crate::flags`]) to set or clear.
|
|
pub flags: Vec<(u32, bool)>,
|
|
/// Alignment (`/Q`): 0 left, 1 centre, 2 right.
|
|
pub quadding: Option<i64>,
|
|
/// Default value (`/DV`): what Reset form restores.
|
|
pub default_value: Option<Option<String>>,
|
|
/// Lock or unlock the field (its widgets' Locked flag).
|
|
pub locked: Option<bool>,
|
|
/// Actions tab: every trigger's action (replacing the field's).
|
|
pub actions: Option<Vec<(crate::Trigger, crate::FieldAction)>>,
|
|
/// Options tab: the mark of a check box or radio button (every widget).
|
|
pub check_style: Option<CheckStyle>,
|
|
/// Position tab: rotate one widget (`/MK /R`) to 0, 90, 180 or 270 degrees counterclockwise.
|
|
/// The widget keeps its rectangle: `/R` turns the appearance inside it (ISO 32000-2 Table 192
|
|
/// turns the annotation relative to the page, and its `/Rect` stays in default user space), so
|
|
/// a tall field turned 90 degrees holds text running up its long side.
|
|
pub rotation: Option<(usize, i64)>,
|
|
}
|
|
|
|
/// A [`FormError::Invalid`] with message `m`.
|
|
fn invalid<T>(m: impl Into<String>) -> Result<T, FormError> {
|
|
Err(FormError::Invalid(m.into()))
|
|
}
|
|
|
|
/// Put `value` into field `f` as its kind takes one (a text value through the field's Keystroke
|
|
/// and Validate events and its scripts first), and redraw the field.
|
|
fn rgb(c: [f64; 3]) -> Object {
|
|
Object::Array(c.iter().map(|v| Object::Real(*v)).collect())
|
|
}
|