Fachartikel

Erstellen von AcroForm-Feldern und -Aktionen mit HotPDF in Delphi

Eine AcroForm-Aktion ist ein Wörterbuch, das an ein Widget angehängt ist und dem Viewer mitteilt, was zu tun ist, wenn mit diesem Widget etwas passiert. Klicken Sie auf eine Schaltfläche, und der Viewer liest ihr Aktionswörterbuch: Eine URI-Aktion öffnet eine Webadresse, eine JavaScript-Aktion führt ein Skript aus, eine SubmitForm-Aktion postet die gesammelten Feldwerte an einen Endpunkt, eine ResetForm-Aktion setzt sie auf die Standardwerte zurück. Die Aktion ist Daten, kein in die Datei eingebranntes Verhalten. ISO 32000-1 §12.6 definiert die Wörterbuchform; der Viewer stellt die Engine bereit, die sie interpretiert. Diese Trennung ist wichtig, da eine perfekt in das PDF geschriebene Aktion dennoch nichts bewirkt, wenn der Reader auf der anderen Seite keine Engine dafür hat, und viel AcroForm-Ärger geht auf diese Lücke zurück anstatt auf ein fehlerhaft geformtes Feld.

HotPDF schreibt diese Wörterbücher direkt aus Delphi und C++Builder, neben den Feld-Widgets, an denen sie hängen. Bei jedem interaktiven Formular sind zwei Strukturen im Spiel: das Widget, das der Benutzer auf der Seite sieht, und die dahinterliegende Feld- plus Aktionsmaschinerie, die die Daten und die Verdrahtung trägt. Sie werden unabhängig voneinander bearbeitet, und eine kann falsch sein, während die andere gut aussieht. Die folgenden Abschnitte befassen sich mit der Feldbenennung, den Schaltflächenaktionen selbst, JavaScript auf Feldebene und der Klasse von Fehlern, die eine visuelle Überprüfung überleben, weil sie vollständig in der zweiten Struktur leben.

Feldnamen sind Routing-Schlüssel, keine Beschriftungen

Jedes AcroForm-Feld trägt einen vollständig qualifizierten Namen. ISO 32000-1 §12.7.3 macht diesen Namen, nicht die sichtbare Beschriftung, zu dem Schlüssel, unter dem der Wert des Feldes übertragen wird, wenn das Formular exportiert oder gesendet wird. Entwickler, die vom VCL-Design kommen, neigen dazu, den Namen eines Steuerelements als privaten Code-Identifikator zu behandeln, und das ist er hier nicht. Es ist das Übertragungsformat.

Daraus folgt zunächst, dass zwei Felder mit demselben vollständig qualifizierten Namen nicht zwei Felder sind. PDF behandelt sie als zwei Widget-Anmerkungen eines einzigen Feldes, die sich einen Wert teilen. Wenn man also in das eine tippt, wird das andere sofort aktualisiert. Das ist genau das, was Sie wollen, wenn ein Kundenname auf jeder Seite eines Vertrags wiederholt werden muss. Es ist ein Fehler, wenn eine Generierungsschleife versehentlich 'Field1' über drei Seiten hinweg wiederverwendet. Keine visuelle Inspektion fängt den zweiten Fall auf. Jede Seite zeichnet immer noch ihr eigenes Kästchen, und die Verknüpfung tritt erst zutage, wenn jemand anfängt zu tippen.

Gepunktete Namen wie applicant.email bauen eine Hierarchie auf. Der übergeordnete Knoten applicant gruppiert seine Kinder, was es ermöglicht, dass ein Zurücksetzen oder Senden nur auf einen Teil eines Formulars abzielt. Felder von Anfang an auf diese Weise zu benennen, kostet nichts, und es zahlt sich beim ersten Mal aus, wenn das empfangende System nur nach dem Bewerberblock fragt.

Für Optionsfelder (Radio Buttons) gilt eine eigene Regel. Schaltflächen, die gemeinsam umgeschaltet werden sollen, müssen einen gemeinsamen Gruppennamen haben. In HotPDF hängen AddRadioButton-Aufrufe, die denselben Gruppennamen übergeben, ihre Widgets an ein übergeordnetes Feld an, und der Exportwert jeder Schaltfläche ('basic' oder 'full') identifiziert die gewählte Option. Geben Sie jeder Schaltfläche einen eigenen Namen, und Sie erhalten eine Reihe unabhängiger Ein/Aus-Schalter anstelle einer sich gegenseitig ausschließenden Gruppe, die identisch gerendert wird und sich falsch verhält.

Erstellen des Feldsatzes Seite für Seite

HotPDF platziert Felder über THPDFPage-Methoden, sodass jedes Feld zu dem Seitenobjekt gehört, das es erstellt hat. Die Sequenzierungsfalle, auf die man achten muss, ist AddPage. Es richtet CurrentPage in dem Moment auf die neue Seite neu aus, in dem es zurückkehrt. Daher landet jeder Feldaufruf danach auf der neuen Seite, selbst wenn das Feld logisch zu der Seite gehörte, die Sie gerade verlassen haben. Beenden Sie jede Seite, gezeichneten Inhalt und Felder zusammen, bevor Sie AddPage aufrufen.

procedure BuildClaimForm(Pdf: THotPDF);
begin
  // Page 1: applicant block
  Pdf.CurrentPage.AddTextField('applicant.name', '', Rect(50, 700, 300, 722));
  Pdf.CurrentPage.AddTextField('applicant.email', '', Rect(50, 660, 300, 682));
  Pdf.CurrentPage.AddCheckBox('consent', 'Y', Rect(50, 620, 70, 640), False);
  Pdf.CurrentPage.AddRadioButton('coverage', 'basic', Rect(50, 580, 70, 600), True);
  Pdf.CurrentPage.AddRadioButton('coverage', 'full', Rect(90, 580, 110, 600), False);
  Pdf.CurrentPage.AddComboBox('plan', 'Standard',
    ['Basic', 'Standard', 'Premium'], Rect(50, 540, 200, 565));

  Pdf.AddPage;  // CurrentPage now points at page 2
  Pdf.CurrentPage.AddListBox('riders', 'None',
    ['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;

Die Koordinaten verwenden die PDF-Konvention, mit dem Ursprung in der unteren linken Ecke der Seite. Dies ist derselbe Ursprung, den TextOut für gezeichneten Text verwendet, sodass Rect(50, 100, 200, 120) nahe am unteren Rand einer Letter-Seite liegt, nicht oben. VCL setzt Y oben an und lässt es nach unten wachsen, sodass eine direkt portierte Layouttabelle vertikal gespiegelt herauskommt, wobei jedes Feld an das falsche Ende der Seite gedreht wird. Führen Sie die Konvertierung einmal in einem gemeinsamen Helfer durch, anstatt an jeder Aufrufstelle, und eine einzige Korrektur repariert das gesamte Formular.

Verdrahten von Schaltflächen mit URI-, JavaScript- und Sendeaktionen

Eine Drucktaste ist inaktiv, bis eine Aktion an sie angehängt wird. HotPDF macht die Aktionstypen aus ISO 32000-1 §12.6.4 über die Enumeration THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed) zugänglich und stellt zwei Methoden bereit, die die Schaltfläche erstellen und ihre Aktion in einem einzigen Aufruf binden.

// Open a help page in the system browser
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
  'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);

// Run viewer-side JavaScript
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
  'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);

// Submit as XFDF and keep empty fields in the payload
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
  'https://api.example.com/claims', Rect(320, 620, 420, 650),
  [sffXFDF, sffIncludeNoValueFields]);

Die Sende-Flags (Submit Flags) verdienen mehr Gedanken, als sie gewöhnlich erhalten. AddPushButtonWithSubmitAction nimmt eine THPDFSubmitFormFlags-Menge, und eine leere Menge erzeugt einen einfachen URL-codierten Post, was das Format ist, das viele Beispiel-Endpunkte akzeptieren und viele Produktions-Endpunkte ablehnen. Das Hinzufügen von sffXFDF schaltet die Nutzlast auf XFDF um. sffGetMethod ändert das HTTP-Verb. sffIncludeNoValueFields behält leere Felder in der Nutzlast, anstatt sie stillschweigend zu verwerfen, was in dem Moment wichtig wird, in dem der Verbraucher "fehlend" von "leer" unterscheidet. Das Flag-Set ist Teil Ihres Schnittstellenvertrags mit dem empfangenden Endpunkt, klären Sie dies also mit dem Team, das die Übermittlung analysiert, nicht erst nach der ersten abgelehnten Charge.

JavaScript auf Feldebene: Tastendruck, Format, Validierung

Klicks auf Schaltflächen sind nicht der einzige Ort, an dem Aktionen leben. HotPDF hängt auch JavaScript an die Ereignisse pro Feld an, die skriptfähige Viewer auslösen, während ein Benutzer Daten eingibt. Es gibt drei Auslöser (Triggers), und sie werden an verschiedenen Punkten im Eingabelebenszyklus ausgelöst. Eine Tastendruck-Aktion (Keystroke) wird ausgeführt, wenn jedes Zeichen ankommt, und erneut beim Bestätigen (Commit). Eine Format-Aktion schreibt den angezeigten Wert nach einem bestätigten Wechsel neu, rein für die Präsentation. Eine Validierungs-Aktion hat das letzte Wort und akzeptiert oder lehnt den bestätigten Wert ab, bevor er zum Wert des Feldes wird.

// Reject committed values that are not plausible email addresses
Pdf.AttachFieldKeyStrokeAction('applicant.email',
  'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');

// Display US phone numbers as (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
  'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');

// Refuse applicants under 18 at commit time
Pdf.AttachFieldValidateAction('applicant.age',
  'if (parseInt(event.value) < 18) event.rc = false;');

Das Setzen von event.rc = false innerhalb eines Tastendruck- oder Validierungsskripts weist den Viewer an, die Eingabe abzulehnen. Der Haken ist, dass nichts davon ausgeführt wird, es sei denn, der Viewer liefert eine JavaScript-Engine mit. Acrobat und einige Desktop-Produkte haben eine. Die meisten mobilen Reader, im Browser eingebettete Renderer und Druck-Pipelines haben keine und ignorieren die Skripte ohne Beschwerde. Feld-Skripte verbessern also die Datenqualität für die Untergruppe der Benutzer, deren Reader sie ausführt, und das ist alles, was sie tun. Sie sind keine Sicherheitsgrenze. Jeder übermittelte Wert muss auf dem Server validiert werden, sobald er ankommt, da Sie nicht davon ausgehen können, dass der Client irgendetwas überprüft hat.

Defekte, die die visuelle Überprüfung bestehen

Die am schwersten zu fangenden AcroForm-Defekte sind diejenigen, die in der Datenstruktur statt im Rendering leben, weil das Öffnen der Datei und das Ansehen Ihnen nichts verrät. Vier tauchen oft genug auf, um es wert zu sein, benannt zu werden, und jeder hat einen mechanischen Test, der ihn vor der Veröffentlichung findet:

  • Drift von Exportwerten. Ein Kontrollkästchen, das als AddCheckBox('consent', 'Yes', ...) erstellt wurde, postet Yes. Ein Verbraucher, der auf Y abgleicht, lehnt jede Übermittlung ab, während die Seite perfekt aussieht. Füllen Sie das Formular aus, exportieren Sie es als XFDF aus Acrobat und vergleichen Sie die Werte mit dem Schema, das der Verbraucher tatsächlich erwartet.
  • Versehentliche Wertespiegelung. Zwei Felder, die sich einen vollständig qualifizierten Namen teilen, verschmelzen zu einem. Das Symptom zeigt sich bei der Dateneingabe und niemals bei der Generierung. Der Test besteht also darin, in das Formular zu tippen, und nicht darin, es zu rendern und das Ergebnis per Augenmaß zu prüfen.
  • Kombinationswerte außerhalb der Optionsliste. Wenn der aktuelle Wert, der an AddComboBox übergeben wird, nicht eine der aufgelisteten Optionen ist, sind sich die Viewer uneinig, ob er angezeigt, ausgeblendet oder markiert werden soll. Belassen Sie den Standardwert innerhalb der Liste, und die Uneinigkeit verschwindet.
  • Felder noch bearbeitbar, nachdem der Workflow geschlossen wurde. HotPDF hat keinen Aufruf zum Abflachen des Erscheinungsbilds für AcroForm-Felder. Die unterstützte Methode zum Einfrieren eines fertigen Formulars besteht darin, die Felder mit dem Flag ffReadOnly zu erstellen, wodurch der Wert durch den eigenen Erscheinungsstrom des Felds sichtbar bleibt, während Bearbeitungen abgelehnt werden. Das Feld bleibt ein lebendiges Formularobjekt, was genau das ist, was nachgelagerte Assemblierungs- und Signaturwerkzeuge erwarten.

Ein viewerseitiges Verhalten ist eine Regressionsnotiz wert, auch wenn keine Codeänderung es behebt. Enterprise-Acrobat-Bereitstellungen können JavaScript deaktivieren oder Sendeziele per Richtlinie einschränken, sodass eine Aktion, die durch jeden Entwicklungs-Build hindurch funktioniert hat, auf einem gesperrten Kunden-Desktop nutzlos sein kann. Planen Sie einen sichtbaren Fallback für den Fall ein, dass die Schaltfläche nichts bewirkt, selbst wenn dieser Fallback nur eine gedruckte Anweisung ist, die dem Benutzer sagt, was er stattdessen tun soll.

Wo Formulararbeit mit dem Rest des Dokuments verbunden ist

Ein Signaturfeld ist selbst ein AcroForm-Feldtyp. Für ein Formular, das später zertifiziert oder gegengezeichnet wird, ist es besser, dieses Feld während der Generierung zu reservieren, als es nachträglich einpatchen zu lassen. Die Gründe auf Byte-Ebene dafür finden sich im Begleitartikel zu digitalen Signaturen und PAdES-Signierung mit HotPDF. Eingaben, die als XFA-Pakete und nicht als native AcroForm ankommen, sind eine andere Situation: Das Abflachen von XFA in AcroForm-Felder ist ein eigener Workflow mit einem eigenen Verlustmodell, da die beiden Formulartechnologien in einer Datei nicht nebeneinander existieren können.

Die hier gezeigten Feld-, Aktions- und Trigger-Methoden sind Teil der standardmäßigen HotPDF-Komponenten-API für Delphi und C++Builder; die Produktseite verlinkt die vollständige Referenz, einschließlich der Überladungen von Feld-Flags und der vollständigen Aufzählung der Submit-Flags.