mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-05 18:19:50 +00:00
* M6: form script messages keep their title, icon and buttons, and every one is shown (#4293, #4294) * M6: a form script message waits for a notice the toast already shows * M6: form script messages past the queue's room are counted and reported * M6: a sentence break in the merged parity note * M6: an object that names no parameter is a message's text, as a String object or a caught error * M6: the count of left-out script messages is said again if another notice replaces it before its time * M6: a full stop between two sentences of the FormCalc parity note * chore: re-run CI (a frame-budget timing test missed on a shared runner)
306 lines
9.5 KiB
Rust
306 lines
9.5 KiB
Rust
/// The kinds of field, as `field.type` names them.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum FieldType {
|
|
Text,
|
|
CheckBox,
|
|
RadioButton,
|
|
ComboBox,
|
|
ListBox,
|
|
Button,
|
|
Signature,
|
|
}
|
|
|
|
impl FieldType {
|
|
pub fn name(self) -> &'static str {
|
|
match self {
|
|
FieldType::Text => "text",
|
|
FieldType::CheckBox => "checkbox",
|
|
FieldType::RadioButton => "radiobutton",
|
|
FieldType::ComboBox => "combobox",
|
|
FieldType::ListBox => "listbox",
|
|
FieldType::Button => "button",
|
|
FieldType::Signature => "signature",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `display.*`: visible, hidden, noPrint, noView.
|
|
pub const DISPLAY_VISIBLE: i32 = 0;
|
|
|
|
pub const DISPLAY_HIDDEN: i32 = 1;
|
|
|
|
pub const DISPLAY_NO_PRINT: i32 = 2;
|
|
|
|
pub const DISPLAY_NO_VIEW: i32 = 3;
|
|
|
|
/// A field as scripts see it.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct FieldState {
|
|
pub name: String,
|
|
pub kind: FieldType,
|
|
/// Text: the text. Check box / radio: the selected export value (empty when off). Choice:
|
|
/// the selected export values.
|
|
pub value: Vec<String>,
|
|
pub default: Vec<String>,
|
|
pub readonly: bool,
|
|
pub required: bool,
|
|
pub display: i32,
|
|
/// Choice options (export value, display text); check boxes and radios: their export
|
|
/// values, one per widget.
|
|
pub options: Vec<(String, String)>,
|
|
pub char_limit: Option<usize>,
|
|
/// `textColor` / `fillColor` as Acrobat colour arrays (`["RGB", r, g, b]` etc.), when set.
|
|
pub text_color: Option<Vec<String>>,
|
|
pub fill_color: Option<Vec<String>>,
|
|
}
|
|
|
|
impl FieldState {
|
|
pub fn new(name: impl Into<String>, kind: FieldType, value: Vec<String>) -> FieldState {
|
|
FieldState {
|
|
name: name.into(),
|
|
kind,
|
|
value,
|
|
default: Vec::new(),
|
|
readonly: false,
|
|
required: false,
|
|
display: DISPLAY_VISIBLE,
|
|
options: Vec::new(),
|
|
char_limit: None,
|
|
text_color: None,
|
|
fill_color: None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn single(&self) -> String {
|
|
self.value.first().cloned().unwrap_or_default()
|
|
}
|
|
}
|
|
|
|
/// The event a script runs for.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct Event {
|
|
/// `Keystroke`, `Validate`, `Calculate`, `Format`, `Mouse Up`, `Open`, …
|
|
pub name: String,
|
|
/// `Field`, `Doc`, `Page`, …
|
|
pub kind: String,
|
|
/// The field the event is for.
|
|
pub target: Option<String>,
|
|
pub value: String,
|
|
pub change: String,
|
|
pub will_commit: bool,
|
|
}
|
|
|
|
impl Event {
|
|
/// A field event (`event.type` = "Field").
|
|
pub fn field(name: &str, target: &str, value: &str) -> Event {
|
|
Event { name: name.into(), kind: "Field".into(), target: Some(target.into()), value: value.into(), change: String::new(), will_commit: true }
|
|
}
|
|
|
|
/// A document event (`event.type` = "Doc"), e.g. Open or WillSave.
|
|
pub fn doc(name: &str) -> Event {
|
|
Event { name: name.into(), kind: "Doc".into(), target: None, value: String::new(), change: String::new(), will_commit: false }
|
|
}
|
|
}
|
|
|
|
/// About the document, for `this.numPages`, `this.info` and the like.
|
|
#[derive(Clone, Debug, Default, PartialEq)]
|
|
pub struct DocInfo {
|
|
pub file_name: String,
|
|
pub num_pages: usize,
|
|
/// The current page, 0-based.
|
|
pub page: usize,
|
|
/// Document information (`this.info.title` etc.): lower-case key, value.
|
|
pub info: Vec<(String, String)>,
|
|
/// The document has an XFA form, so scripts see `xfa_installed`.
|
|
pub xfa: bool,
|
|
}
|
|
|
|
/// The icon a script's message shows: `app.alert`'s `nIcon` and `xfa.host.messageBox`'s
|
|
/// icon, numbered 0 to 3 in the JavaScript for Acrobat API Reference and the XFA
|
|
/// specification alike.
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub enum MessageIcon {
|
|
/// 0, the default.
|
|
#[default]
|
|
Error,
|
|
/// 1.
|
|
Warning,
|
|
/// 2.
|
|
Question,
|
|
/// 3.
|
|
Status,
|
|
}
|
|
|
|
impl MessageIcon {
|
|
/// The icon numbered `code`; a missing, fractional or out-of-range number is the default.
|
|
pub fn from_code(code: Option<f64>) -> MessageIcon {
|
|
match whole_code(code) {
|
|
Some(1) => MessageIcon::Warning,
|
|
Some(2) => MessageIcon::Question,
|
|
Some(3) => MessageIcon::Status,
|
|
_ => MessageIcon::Error,
|
|
}
|
|
}
|
|
|
|
/// A stable name for tools and logs: `error`, `warning`, `question` or `status`.
|
|
pub fn name(self) -> &'static str {
|
|
match self {
|
|
MessageIcon::Error => "error",
|
|
MessageIcon::Warning => "warning",
|
|
MessageIcon::Question => "question",
|
|
MessageIcon::Status => "status",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The buttons a script's message offers: `app.alert`'s `nType` and `xfa.host.messageBox`'s
|
|
/// buttons, numbered 0 to 3 in both references.
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub enum MessageButtons {
|
|
/// 0, the default.
|
|
#[default]
|
|
Ok,
|
|
/// 1.
|
|
OkCancel,
|
|
/// 2.
|
|
YesNo,
|
|
/// 3.
|
|
YesNoCancel,
|
|
}
|
|
|
|
impl MessageButtons {
|
|
/// The buttons numbered `code`; a missing, fractional or out-of-range number is the default.
|
|
pub fn from_code(code: Option<f64>) -> MessageButtons {
|
|
match whole_code(code) {
|
|
Some(1) => MessageButtons::OkCancel,
|
|
Some(2) => MessageButtons::YesNo,
|
|
Some(3) => MessageButtons::YesNoCancel,
|
|
_ => MessageButtons::Ok,
|
|
}
|
|
}
|
|
|
|
/// A stable name for tools and logs: `ok`, `ok_cancel`, `yes_no` or `yes_no_cancel`.
|
|
pub fn name(self) -> &'static str {
|
|
match self {
|
|
MessageButtons::Ok => "ok",
|
|
MessageButtons::OkCancel => "ok_cancel",
|
|
MessageButtons::YesNo => "yes_no",
|
|
MessageButtons::YesNoCancel => "yes_no_cancel",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `code` as one of the numbers 0 to 3 the references define, when it is exactly one.
|
|
fn whole_code(code: Option<f64>) -> Option<u8> {
|
|
let c = code.filter(|c| c.is_finite() && c.fract() == 0.0 && (0.0..=3.0).contains(c))?;
|
|
// In 0..=3 and whole, so the cast is exact.
|
|
Some(c as u8)
|
|
}
|
|
|
|
/// A message a script showed (`app.alert`, `xfa.host.messageBox`), with the title, icon and
|
|
/// buttons it asked for. Its text and title are cut to a message's length.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
pub struct Message {
|
|
pub text: String,
|
|
/// The dialog's title, when the script gave one.
|
|
pub title: Option<String>,
|
|
pub icon: MessageIcon,
|
|
pub buttons: MessageButtons,
|
|
}
|
|
|
|
impl Message {
|
|
/// A message with only text: no title, the default icon and an OK button.
|
|
pub fn plain(text: impl Into<String>) -> Message {
|
|
Message { text: text.into(), ..Default::default() }
|
|
}
|
|
|
|
/// Bytes of text the message holds, charged to a run's budget.
|
|
pub(crate) fn bytes(&self) -> usize {
|
|
self.text.len().saturating_add(self.title.as_ref().map_or(0, String::len))
|
|
}
|
|
}
|
|
|
|
/// Something a script asked the viewer to do.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum Request {
|
|
/// `this.resetForm([names])` (empty: every field).
|
|
Reset(Vec<String>),
|
|
Print,
|
|
/// `this.pageNum = n` (0-based).
|
|
GoToPage(usize),
|
|
LaunchUrl(String),
|
|
/// `this.submitForm(url)`: never sent on PdfCraft's own.
|
|
Submit(String),
|
|
/// `field.setFocus()`.
|
|
Focus(String),
|
|
Beep,
|
|
/// `app.execMenuItem("SaveAs")` (XFA forms' Save buttons).
|
|
SaveAs,
|
|
/// `app.execMenuItem("ShowHide…")`: show the side panel, or hide it when it is showing.
|
|
ShowPanel(Panel),
|
|
}
|
|
|
|
/// A side panel a script can show or hide (`app.execMenuItem("ShowHideFileAttachment")`, …).
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum Panel {
|
|
/// The document's bookmarks (outline).
|
|
Bookmarks,
|
|
/// Page thumbnails.
|
|
Pages,
|
|
/// The files attached to the document.
|
|
Attachments,
|
|
/// The document's signatures and their validity.
|
|
Signatures,
|
|
/// The document's layers (optional content).
|
|
Layers,
|
|
}
|
|
|
|
impl Panel {
|
|
/// The panel's name in automation output, fixed whatever the variant is called.
|
|
pub fn id(self) -> &'static str {
|
|
match self {
|
|
Panel::Bookmarks => "bookmarks",
|
|
Panel::Pages => "pages",
|
|
Panel::Attachments => "attachments",
|
|
Panel::Signatures => "signatures",
|
|
Panel::Layers => "layers",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// What running a script did.
|
|
#[derive(Clone, Debug, Default, PartialEq)]
|
|
pub struct Outcome {
|
|
/// `event.rc`: `false` rejects the keystroke or value.
|
|
pub rc: bool,
|
|
/// `event.value` after the script.
|
|
pub value: String,
|
|
pub change: String,
|
|
/// Fields whose value or properties the script changed, as they are now.
|
|
pub changed: Vec<FieldState>,
|
|
/// The messages the script showed, in order.
|
|
pub alerts: Vec<Message>,
|
|
pub console: Vec<String>,
|
|
pub requests: Vec<Request>,
|
|
/// The script's error (syntax error, exception, or a limit reached), if it failed.
|
|
pub error: Option<String>,
|
|
/// Errors in the document-level scripts that ran first. Each is a script of its own: one
|
|
/// that throws stops where it failed, and the others and the script itself still run (#3940).
|
|
pub doc_errors: Vec<String>,
|
|
/// The script's completion value (what the console prints), unless undefined.
|
|
pub result: Option<String>,
|
|
}
|
|
|
|
/// Bounds that keep a script from hanging the viewer.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct Limits {
|
|
pub loop_iterations: u64,
|
|
pub recursion: usize,
|
|
}
|
|
|
|
impl Default for Limits {
|
|
fn default() -> Self {
|
|
Limits { loop_iterations: 1_000_000, recursion: 256 }
|
|
}
|
|
}
|