XFA, die XML Forms Architecture, ist veraltet. ISO 32000-1 führt sie in §12.7 mit dem Hinweis auf, dass sie aus PDF 2.0 entfernt wurde, und moderne Viewer stellen ihre XFA-Engines nacheinander ein. Nichts davon hat jedoch die Archive geleert. Behördliche Aufnahmeformulare, Versicherungsanträge und Bankbelege wurden fast zwei Jahrzehnte lang als XFA verfasst, und diese Dateien gehen auch heute noch in Posteingängen und Dokumenten-Pipelines ein. Wenn der Viewer, der sie früher gerendert hat, dies nicht mehr tut, verwandelt sich das Formular in eine leere Seite mit einem Platzhalter "Bitte in einem anderen Reader öffnen". Die dauerhafte Lösung besteht darin, das XFA in statischen PDF-Inhalt zu flachen (flatten), den jeder Reader darstellen kann.
Der schwierige Teil dieses Flattening-Prozesses sind nicht die Felder. Textfelder und Kontrollkästchen lassen sich sauber genug auf AcroForm-Widgets abbilden. Der schwierige Teil ist der Rich Text, den XFA in einem Draw-Element in einem <exData contentType="text/html">-Block speichert. Dieser Block ist eine HTML-Untermenge mit Inline-Styling und häufig auch Ankern (Links). Diesen Text auf die Seite zu bringen bedeutet, sowohl den gestalteten Text als auch die aktiven Hyperlinks zu reproduzieren – und bei den Hyperlinks geben die meisten Implementierungen stillschweigend auf.
Wie XFA Rich Text tatsächlich aussieht
Ein exData-Körper ist ein kleiner Ausschnitt aus XHTML. Ein Absatz ist ein <p>; eine gestaltete Zeichenfolge ist ein <span> mit eigenem Inline-CSS für Stärke, Schriftstil, Farbe und Größe; und ein Hyperlink ist ein <a href="...">, der den sichtbaren Text umschließt. Eine einzelne Zeile kann mehrere Spans hintereinander enthalten, jeweils mit unterschiedlichem Styling, und einer davon kann ein Anker sein. Das Styling ist keine Dekoration, die man einfach weglassen kann. Eine Klausel, die fett und rot dargestellt wird, weil es sich um eine rechtliche Warnung handelt, muss auch nach dem Flatten fett und rot bleiben, andernfalls verfälscht das geflashte Dokument das Original.
Die Flatten-Engine kann den Block also nicht als eine einzige Zeichenfolge behandeln. Sie muss die Inline-Struktur durchlaufen, den effektiven Stil jedes Textlaufs auflösen, indem sie das Inline-CSS des Spans über die Basisschriftart des Draw-Elements legt, und die Läufe nacheinander über die Zeile anordnen. HotPDF modelliert jedes dieser angeordneten Fragmente als internen TXFARichRun-Datensatz. Der Datensatz enthält den Text des Laufs, seinen aufgelösten Stil, seine gemessene Box und, im Falle eines Ankers, die Href, auf die er verweist.
Anordnung der Läufe von links nach rechts
Bei der Positionierung hört Rich Text auf, ein reines Parsing-Problem zu sein, und wird zu einem Satz-Problem. Die Läufe teilen sich eine Zeile, sodass jeder Lauf dort beginnt, wo der vorherige endete. Es gibt kein Markup, das diese Positionen aufzeichnet; sie müssen gemessen werden. Die interne Routine der Engine, LayoutRichText, misst jeden Lauf mit denselben Schriftmetriken, die ihn später zeichnen werden, und setzt dann den horizontalen Versatz des Laufs auf die laufende Summe aller vorherigen Laufbreiten. Lauf eins beginnt am Ursprung der Draw-Box, Lauf zwei beginnt bei der Breite von Lauf eins, Lauf drei bei der kombinierten Breite der ersten beiden und so weiter über die Zeile.
This is why measurement font alignment matters so much. The layout pass measures advances; a separate render pass draws glyphs. If those two passes disagree about the font, the boxes the layout computed will not sit under the glyphs the renderer paints. HotPDF keeps them in step by mapping each run's resolved style onto a font specification, through the internal RunStyleToFontSpec helper, that matches the renderer's own defaults of Arial at 10 points. The measured advance and the drawn text then agree, and a run's computed box genuinely covers the characters a reader sees.
// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
TRichRunInfo = record
Dx, Dy : Double; // top-left, relative to the draw-box origin
W, H : Double; // measured run box (width from the layout pass)
Text : AnsiString; // the run's visible characters
Href : AnsiString; // URI target for an <a> run, '' otherwise
end;
Vom Anker-Lauf zur PDF-Link-Annotation
Ein Hyperlink in einem fertigen PDF ist kein Teil des Seiteninhalts. Es ist ein separates Objekt, eine Link-Annotation, beschrieben in ISO 32000-1 §12.5.6.5. Die Annotation besitzt ein /Rect, das das klickbare Rechteck auf der Seite definiert, und eine Aktion, die ausgelöst wird, wenn auf das Rechteck geklickt wird. Bei einem externen Link handelt es sich um eine URI-Aktion: /S /URI mit der Zieladresse als /URI-Zeichenfolge. Der sichtbare Text darunter ist gewöhnlicher Seiteninhalt; die Annotation ist der unsichtbare Klickbereich (Hot Zone), der darüber gelegt wird.
Der Flatten-Pfad folgt genau diesem Modell. Wenn ein Lauf eine Href trägt, zeichnet HotPDF zuerst den gestalteten Text und erstellt dann eine Link-Annotation über der Box des Laufs. Der öffentliche Einstiegspunkt für diese Annotation ist die Seitenmethode AddURILink, die das Objekt /Type /Annot /Subtype /Link mit einer /URI-Aktion erstellt und das Annotations-Dictionary zurückgibt. Ihr Rechteck ist die gemessene Box des Laufs, übersetzt aus den lokalen Koordinaten des Draw-Elements in Seitenkoordinaten. Das Ergebnis ist ein Link, der präzise auf dem Ankertext landet und nirgendwo anders.
// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // page-space hit box for the run
Annot := Pdf.CurrentPage.AddURILink(LinkRect,
'https://www.example.gov/appeal', 'File an appeal online');
end;
Warum der Klickbereich aus gemessenen Breiten stammen muss
Es ist verlockend anzunehmen, dass man den Link lokalisieren kann, indem man die Seite nach ihrem sichtbaren Text durchsucht und das Rechteck um das Gefundene zeichnet. Das funktioniert jedoch nicht, und der Grund liegt darin, wie geflashtet Text gespeichert wird. Die gestalteten Läufe werden mit eingebetteten Subset-Schriftarten gezeichnet. Eine Subset-Schriftart nummeriert die Glyphen, die sie behält, neu, sodass der Seiteninhaltsstrom hexadezimale CID-Codes und nicht die ursprünglichen Zeichencodes enthält. Die Bytes auf der Seite sind nicht die Buchstaben, die ein Mensch liest, und sie sind nicht als Text durchsuchbar. Eine Suche nach der Beschriftung des Ankers findet nichts, da diese Beschriftung nirgendwo im Datenstrom als Klartext existiert.
Der einzige verlässliche Anker für das Rechteck ist die Geometrie, die der Layout-Durchlauf bereits erzeugt hat. Der Versatz und die gemessene Breite jedes Laufs wurden während des Zeilenflusses berechnet, bevor Glyphen neu nummeriert wurden, und sie beschreiben, wo der Text physisch erscheinen wird. HotPDF übernimmt das Link-Rechteck daher direkt aus der gezeichneten Box des Laufs und nicht aus einer Textsuche. Da die Messung die Renderschriftart verwendete, ist die Box unabhängig vom Subsetting korrekt. Die Geometrie übersteht die Kodierung; der Text nicht. Dies ist das Hauptargument für eine Positionierung über gemessene Breiten, und es erklärt, warum ein Tool, das versucht, Links nachträglich per Textsuche einzufügen, Klickbereiche erzeugt, die sich verschieben oder verschwinden.
Steuern des Flattenings über Ihren Code
Für ein PDF, das bereits ein XFA-Paket enthält, ist der Einstiegspunkt FlattenLoadedXFA. Laden Sie das Dokument, rufen Sie die Methode auf und speichern Sie das Ergebnis. Der Parameter Editable entscheidet, was mit den Formularfeldern geschieht: Übergeben Sie True, um sie als ausfüllbare AcroForm-Widgets zu behalten, oder False, um jedes Widget als schreibgeschützt zu markieren, sodass die Ausgabe ein fixiertes Dokument ist. Die Rich-Text-Zeichenblöcke mit ihren gestalteten Läufe und Link-Annotationen werden in jedem Fall erzeugt. Die Funktion gibt die Anzahl der ausgegebenen Widgets zurück.
var
Pdf: THotPDF;
Emitted, i: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('xfa_appeal_form.pdf');
// True keeps fields fillable; False freezes them read-only.
Emitted := Pdf.FlattenLoadedXFA(True);
// Anything the engine could not map is reported, not raised.
for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);
Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
Writeln('Widgets emitted: ', Emitted);
finally
Pdf.Free;
end;
end;
Lesen Sie nach dem Aufruf immer XFAFlattenWarnings aus. Die Liste wird zu Beginn jedes Flattenings geleert und sammelt eine Zeile für jedes Element, das die Engine nicht rendern konnte: eine nicht unterstützte Feldart, ein Bild, das nicht dekodiert werden konnte, oder ein exData-Block ohne verwendbare Spans. Keiner dieser Fälle löst eine Ausnahme aus, sodass eine leere Warnungsliste Ihr Beweis dafür ist, dass alles erfolgreich abgebildet wurde, und eine nicht leere Liste Ihnen genau zeigt, welche Originale Sie untersuchen müssen. Wenn Sie die XFA-Rohdaten als XDP-Bytes anstelle einer geladenen PDF-Datei vorliegen haben, übernimmt die Schwestermethode ApplyXFAAsAcroForm diese Bytes direkt und teilt sich denselben Codepfad sowie dasselbe Warnungsverhalten. Die komplementäre Methode AddXFAPacket geht den umgekehrten Weg und bettet ein XFA-Paket in ein Dokument ein, das Sie gerade erstellen.
Bestätigung des Ergebnisses in einem Reader
Öffnen Sie die geflashtete Datei in Acrobat oder einem anderen aktuellen Viewer und überprüfen Sie zwei Dinge. Erstens, ob der Rich Text mit intaktem Styling gerendert wurde: Die fetten Läufe sind fett, die farbigen Läufe tragen ihre Farbe und die Spans liegen in der richtigen Reihenfolge auf der Zeile, anstatt sich zu überlappen oder aus der Box herauszulaufen. Zweitens, ob die Hyperlinks aktiv sind. Bewegen Sie den Mauszeiger über einen Anker; die Statusleiste sollte die Zieladresse anzeigen. Klicken Sie darauf, sollte sich die URI-Aktion öffnen. Verwenden Sie den Annotations-Inspektor des Viewers, um zu bestätigen, dass es sich bei jedem Link um eine echte /Link-Annotation handelt, deren /Rect sich an den Ankertext schmiegt und über Inhalten liegt, die nun einfache gezeichnete Glyphen und keine XFA-gerenderten Formulare mehr sind. Diese Kombination – gestalteter statischer Text plus echte Link-Annotationen auf den richtigen Rechtecken – sorgt dafür, dass das geflashtete Dokument die XFA-Engines überdauert, die es nicht mehr benötigt.
Das Flattening der Felder selbst – der Textfelder, Kontrollkästchen und Auswahllisten, die diesen Rich Text umgeben – wird in unserem Leitfaden zum Flattening von XFA-Formularen in AcroForm-Widgets behandelt. Für das weiterführende Thema der manuellen Erstellung und Platzierung von Link-Annotationen (über die vom Flatten-Pfad generierten hinaus) lesen Sie Arbeiten mit PDF-Annotationen in HotPDF. Beide bauen auf demselben Annotations- und Formularmodell auf, das mit der HotPDF-Komponente für Delphi und C++Builder ausgeliefert wird.