XFA, de XML Forms Architecture, is verouderd (deprecated). ISO 32000-1 neemt het op in §12.7 met de opmerking dat het is verwijderd uit PDF 2.0, en moderne viewers laten hun XFA-engines één voor één vallen. Niets daarvan heeft de archieven echter leeggemaakt. Intakeformulieren van de overheid, verzekeringsaanvragen en bankafschriften werden gedurende het grootste deel van twee decennia opgesteld als XFA, en die bestanden belanden vandaag de dag nog steeds in inboxen en documentpipelines. Wanneer de viewer die ze vroeger renderde daarmee stopt, verandert het formulier in een blanco pagina met een "open a.u.b. in een andere lezer" aanduiding ("placeholder"). De duurzame oplossing is om de XFA af te vlakken ("flattening") naar statische PDF-content die elke lezer kan weergeven ("paint")
Het moeilijke deel van dat afvlakken zijn niet de velden. Tekstvakken en selectievakjes mappen netjes genoeg naar AcroForm-widgets. Het moeilijke deel is de rich text die XFA opslaat in een tekenelement ("draw element"), in een <exData contentType="text/html">-blok. Dat blok is een HTML-subset met inline styling en, vaak, ankers. Het op de pagina krijgen betekent het reproduceren van zowel de opgemaakte tekst als de live hyperlinks, en de hyperlinks zijn het punt waar de meeste implementaties stilletjes opgeven
Hoe XFA rich text er daadwerkelijk uitziet
Een exData-body is een klein stukje XHTML. Een alinea is een <p>; een opgemaakt bereik ("span") van tekens is een <span> met zijn eigen inline CSS voor gewicht, houding, kleur en grootte; en een hyperlink is een <a href="..."> die zijn zichtbare tekst omwikkelt. Een enkele regel kan meerdere spans op een rij bevatten, elk met een andere styling, en één daarvan kan een anker zijn. De styling is geen decoratie die weggelaten kan worden. Een clausule die vet en rood wordt weergegeven omdat het een juridische waarschuwing is, moet na het afvlakken vet en rood blijven, anders geeft het afgevlakte document het origineel verkeerd weer
Dus de flatten-engine kan het blok niet als één string behandelen. Het moet door de inline structuur wandelen, de effectieve stijl van elke reeks ("run") bepalen door de inline CSS van de span over het basislettertype van het tekenelement heen te leggen, en de runs één voor één over de regel opmaken. HotPDF modelleert elk van deze opgemaakte fragmenten als een intern TXFARichRun-record. Het record draagt de tekst van de run, zijn vastgestelde stijl, zijn gemeten box en, voor een anker, de Href waar het naar verwijst
De reeksen ("runs") van links naar rechts opmaken
Positionering is waar rich text ophoudt een parsingprobleem te zijn en een zetwerkprobleem wordt. De runs delen een regel, dus elke run begint waar de vorige eindigde. Er is geen opmaak ("markup") die die posities vastlegt; ze moeten gemeten worden. De interne LayoutRichText-routine van de engine meet elke run met dezelfde lettertype-metrics die deze later zullen weergeven, en stelt vervolgens de horizontale verschuiving van de run in op de lopende som van alle voorgaande run-breedtes. Run één begint bij de oorsprong van de draw-box, run twee begint bij de breedte van run één, run drie bij de gecombineerde breedte van de eerste twee, enzovoort over de regel
Dit is waarom uitlijning van het meetlettertype zo belangrijk is. De lay-out-pass meet advances; een afzonderlijke render-pass tekent glyphs. Als die twee passes het oneens zijn over het lettertype, zullen de boxes die de lay-out heeft berekend niet onder de glyphs zitten die de renderer tekent. HotPDF houdt ze in de pas door de vastgestelde stijl van elke run af te beelden op een lettertypespecificatie, via de interne RunStyleToFontSpec-helper, die overeenkomt met de eigen standaardwaarden van de renderer: Arial op 10 punten. De gemeten advance en de getekende tekst komen dan overeen, en de berekende box van een run bedekt daadwerkelijk de tekens die een lezer ziet
// 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;
Van een ankerreeks naar een PDF Link-annotatie
Een hyperlink in a voltooide PDF is geen onderdeel van de paginacontent. Het is een afzonderlijk object, een Link-annotatie, beschreven in ISO 32000-1 §12.5.6.5. De annotatie heeft een /Rect die de klikbare rechthoek op de pagina definieert en een actie die vuurt wanneer op de rechthoek wordt geklikt. Voor een externe link is de actie een URI-actie: /S /URI met het doeladres als de /URI-string. De zichtbare tekst eronder is gewone paginacontent; de annotatie is de onzichtbare "hot zone" die eroverheen wordt gelegd
Het afvlak-pad volgt precies dit model. Wanneer een run een Href draagt, tekent HotPDF eerst de opgemaakte tekst, en bouwt vervolgens een Link-annotatie over de box van de run. Het openbare ingangspunt voor die annotatie is de paginamethode AddURILink, die het /Type /Annot /Subtype /Link-object met een /URI-actie aanmaakt en de annotatiedictionary retourneert. Zijn rechthoek is de gemeten box van de run, vertaald vanuit de lokale coördinaten van het tekenelement naar paginacoördinaten. Het resultaat is een link die precies op de ankertekst landt en nergens 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;
Waarom de hit-box moet voortkomen uit gemeten breedtes
Het is verleidelijk om u voor te stellen dat u de link lokaliseert door op de pagina te zoeken naar de zichtbare tekst en de rechthoek te tekenen om wat er wordt gevonden. Dat werkt niet, en de reden is fundamenteel voor hoe afgevlakte tekst wordt opgeslagen. De opgemaakte runs worden getekend met ingebedde subset-lettertypen. Een subset-lettertype hernummert de glyphs die het behoudt, dus de paginacontent-stream bevat hexadecimale CID-codes, niet de originele tekencodes. De bytes op de pagina zijn niet de letters die een mens leest, en ze zijn niet doorzoekbaar als tekst. Een zoekopdracht naar de titel van het anker vindt niets, omdat die titel nergens in de stream als letterlijke tekst bestaat
Het enige betrouwbare anker voor de rechthoek is de geometrie die de lay-out-pass al heeft geproduceerd. De offset en gemeten breedte van elke run werden berekend tijdens het laten stromen van de regel, voordat een glyph werd gehernummerd, en ze beschrijven waar de tekst fysiek zal verschijnen. HotPDF haalt de link-rechthoek daarom rechtstreeks uit de neergelegde ("laid-down") box van de run, in plaats van uit een tekst-lookup. Omdat de meting het render-lettertype gebruikte, is de box correct, ongeacht subsetting. Geometrie overleeft de codering; tekst niet. Dat is het hele argument voor positionering op basis van gemeten breedte, en het is waarom een flattener die probeert achteraf links in te bouwen door middel van tekst zoeken, hit-zones produceert die afdrijven of verdwijnen
Het afvlakken (flatten) aansturen vanuit uw code
Voor een PDF die al een XFA-pakket bevat, is het ingangspunt FlattenLoadedXFA. Laad het document, roep de methode aan, en sla het resultaat op. De Editable-parameter beslist wat er gebeurt met de formuliervelden: geef True door om ze als invulbare AcroForm-widgets te behouden, of False om elke widget als alleen-lezen te markeren, zodat de uitvoer een bevroren document is. De rich-text draw-blokken, met hun opgemaakte runs en link-annotaties, worden hoe dan ook geproduceerd. De functie retourneert het aantal widgets dat het heeft uitgestuurd
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;
Lees na de aanroep altijd XFAFlattenWarnings uit. De lijst wordt aan het begin van elke flatten gewist en accumuleert een regel voor elk element dat de engine weigerde weer te geven: een niet-ondersteund veldtype, een tekengafbeelding die niet decodeerde, een exData-blok zonder bruikbare spans. Geen daarvan veroorzaakt een uitzondering, dus een lege waarschuwingslijst is uw bewijs dat alles in kaart is gebracht, en een niet-lege vertelt u precies welke originelen u moet inspecteren. Wanneer u de ruwe XFA vasthoudt als XDP-bytes in plaats van een geladen PDF, neemt de "sibling"-methode ApplyXFAAsAcroForm die bytes rechtstreeks aan en deelt hetzelfde codepad en hetzelfde waarschuwingsgedrag. De complementaire AddXFAPacket-methode gaat de andere kant op, waarbij een XFA-pakket wordt ingebed in een document dat u aan het bouwen bent
Het resultaat bevestigen in een lezer ("reader")
Open het afgevlakte bestand in Acrobat, of een andere actuele viewer, en controleer twee dingen. Ten eerste, dat de rich text is weergegeven met behoud van styling: de vetgedrukte runs zijn vetgedrukt, de gekleurde runs dragen hun kleur, en de spans zitten in de juiste volgorde op de regel in plaats van elkaar te overlappen of buiten de box te lopen. Ten tweede, dat de hyperlinks live zijn. Zweef boven een anker en de statusbalk zou het doeladres moeten tonen; klik erop en de URI-actie zou het moeten openen. Gebruik de annotatie-inspecteur van de viewer om te bevestigen dat elk ervan een echte /Link-annotatie is, waarvan de /Rect de ankertekst omsluit, zittend over content die nu slechts geverfde glyphs is in plaats van formulier-gerenderde XFA. Die combinatie, opgemaakte statische tekst plus echte Link-annotaties op de juiste rechthoeken, is wat ervoor zorgt dat het afgevlakte document langer meegaat dan de XFA-engines die het niet langer nodig heeft
Het afvlakken van de velden zelf, de tekstvakken, selectievakjes en keuzelijsten die deze rich text omringen, wordt behandeld in onze walkthrough over het afvlakken van XFA-formulieren naar AcroForm-widgets. Voor het bredere verhaal van het handmatig bouwen en plaatsen van Link-annotaties, los van degene die het flatten-pad genereert, raadpleegt u werken met PDF-annotaties in HotPDF. Beide bouwen voort op hetzelfde annotatie- en formuliermodel dat wordt meegeleverd met de HotPDF Component voor Delphi en C++Builder