Articolo tecnico

Da rich text XFA a link PDF nativi in Delphi con HotPDF

XFA, la XML Forms Architecture, è deprecata. ISO 32000-1 la riporta nel §12.7 con la nota che è stata rimossa da PDF 2.0, e i visualizzatori moderni stanno abbandonando i propri motori XFA uno dopo l'altro. Nulla di tutto ciò ha svuotato gli archivi. Moduli di acquisizione della pubblica amministrazione, richieste assicurative ed estratti conto bancari sono stati creati in XFA per buona parte di due decenni, e quei file continuano ad arrivare oggi nelle caselle di posta e nelle pipeline documentali. Quando il visualizzatore che li rendeva smette di farlo, il modulo diventa una pagina bianca con un segnaposto "please open in a different reader". La correzione duratura è appiattire l'XFA in contenuto PDF statico che qualsiasi reader sappia dipingere

La parte difficile di questo appiattimento non sono i campi. Le caselle di testo e le caselle di spunta si mappano abbastanza bene sui widget AcroForm. La parte difficile è il rich text che XFA memorizza dentro un elemento draw, in un blocco <exData contentType="text/html">. Quel blocco è un sottoinsieme di HTML con stile inline e, spesso, ancore. Portarlo sulla pagina significa riprodurre sia il testo formattato sia i collegamenti attivi, e i collegamenti sono il punto in cui la maggior parte delle implementazioni rinuncia in silenzio

Che aspetto ha davvero il rich text XFA

Un corpo exData è una piccola fetta di XHTML. Un paragrafo è un <p>; una porzione di caratteri formattata è uno <span> con il proprio CSS inline per spessore, inclinazione, colore e corpo; e un collegamento ipertestuale è un <a href="..."> che avvolge il testo visibile. Una singola riga può contenere diversi span di fila, ciascuno con una formattazione diversa, e uno di essi può essere un'ancora. La formattazione non è decorazione che si possa buttare via. Una clausola resa in grassetto rosso perché è un avvertimento legale deve restare in grassetto rosso dopo l'appiattimento, altrimenti il documento appiattito travisa l'originale

Il motore di appiattimento non può quindi trattare il blocco come una stringa unica. Deve percorrere la struttura inline, risolvere lo stile effettivo di ogni run sovrapponendo il CSS inline dello span al font di base dell'elemento draw, e disporre i run uno dopo l'altro lungo la riga. HotPDF modella ciascuno di questi frammenti impaginati come un record interno TXFARichRun. Il record porta con sé il testo del run, il suo stile risolto, il suo riquadro misurato e, per un'ancora, l'Href a cui punta

Disporre i run da sinistra a destra

Il posizionamento è il punto in cui il rich text smette di essere un problema di parsing e diventa un problema di composizione tipografica. I run condividono una riga, quindi ciascuno comincia dove finisce il precedente. Non esiste markup che registri quelle posizioni: vanno misurate. La routine interna LayoutRichText del motore misura ogni run con le stesse metriche di font che poi lo dipingeranno, quindi imposta lo scostamento orizzontale del run alla somma progressiva delle larghezze di tutti i run precedenti. Il primo run parte dall'origine del riquadro draw, il secondo parte alla larghezza del primo, il terzo alla larghezza combinata dei primi due, e così via lungo la riga

Ecco perché l'allineamento del font di misura conta così tanto. Il passaggio di layout misura gli avanzamenti; un passaggio di rendering separato disegna i glifi. Se i due passaggi non concordano sul font, i riquadri calcolati dal layout non staranno sotto i glifi dipinti dal renderer. HotPDF li tiene allineati mappando lo stile risolto di ogni run su una specifica di font, tramite la funzione interna RunStyleToFontSpec, che corrisponde ai valori predefiniti del renderer stesso, ossia Arial a 10 punti. L'avanzamento misurato e il testo disegnato concordano allora, e il riquadro calcolato di un run copre davvero i caratteri che il lettore vede

Diagramma di HotPDF che appiattisce in Delphi un blocco rich text exData XFA in run formattati disposti da sinistra a destra con larghezze misurate, dove il run ancora conserva il suo href e ogni frammento diventa un record TXFARichRun
Il motore di appiattimento percorre la struttura inline di exData, risolve lo stile di ogni run e misura le larghezze con il font di rendering, così i riquadri impaginati stanno esattamente sotto i glifi dipinti
// Forma concettuale di un run impaginato. Il motore ne costruisce internamente
// un array; non li create mai voi, ma i campi spiegano come il riquadro
// sensibile di un link derivi dalla geometria misurata anziché dal testo.
type
  TRichRunInfo = record
    Dx, Dy : Double;       // angolo alto a sinistra, relativo al riquadro draw
    W, H   : Double;       // riquadro del run misurato (larghezza dal layout)
    Text   : AnsiString;   // i caratteri visibili del run
    Href   : AnsiString;   // destinazione URI per un run <a>, '' altrimenti
  end;

Dal run ancora a un'annotazione Link PDF

Un collegamento ipertestuale in un PDF finito non fa parte del contenuto della pagina. È un oggetto separato, un'annotazione Link, descritta in ISO 32000-1 §12.5.6.5. L'annotazione ha un /Rect che definisce il rettangolo cliccabile sulla pagina e un'azione che scatta quando il rettangolo viene cliccato. Per un collegamento esterno l'azione è un'azione URI: /S /URI con l'indirizzo di destinazione come stringa /URI. Il testo visibile sottostante è normale contenuto di pagina; l'annotazione è la zona sensibile invisibile stesa sopra di esso

Il percorso di appiattimento segue esattamente questo modello. Quando un run porta un Href, HotPDF disegna prima il testo formattato, poi costruisce un'annotazione Link sopra il riquadro del run. Il punto di ingresso pubblico per quell'annotazione è il metodo di pagina AddURILink, che crea l'oggetto /Type /Annot /Subtype /Link con un'azione /URI e restituisce il dizionario dell'annotazione. Il suo rettangolo è il riquadro misurato del run, tradotto dalle coordinate locali dell'elemento draw in coordinate di pagina. Il risultato è un collegamento che cade precisamente sul testo dell'ancora e da nessun'altra parte

Diagramma del percorso di appiattimento HotPDF che traduce in coordinate di pagina il riquadro misurato locale al draw di un run ancora ed emette un'annotazione con Subtype Link e azione URI il cui Rect abbraccia il testo dell'ancora
Un run ancora diventa testo dipinto più un'annotazione Link il cui /Rect è il riquadro misurato del run tradotto in coordinate di pagina, con l'azione URI creata da AddURILink
// La stessa API pubblica che il percorso di appiattimento usa per ogni run
// ancora. Produce un'annotazione Link di ISO 32000-1 12.5.6.5: /Subtype /Link
// con un'azione /URI sul rettangolo indicato. La descrizione opzionale riempie
// /Contents perché uno screen reader possa annunciare la destinazione.
var
  LinkRect: TRect;
  Annot: THPDFDictionaryObject;
begin
  LinkRect := Rect(72, 690, 268, 706);  // riquadro sensibile in spazio pagina
  Annot := Pdf.CurrentPage.AddURILink(LinkRect,
    'https://www.example.gov/appeal', 'File an appeal online');
end;

Perché il riquadro sensibile deve venire dalle larghezze misurate

Viene la tentazione di immaginare di localizzare il collegamento cercando nella pagina il suo testo visibile e disegnando il rettangolo attorno a quel che si trova. Non funziona, e la ragione è fondamentale rispetto al modo in cui il testo appiattito viene memorizzato. I run formattati sono dipinti con font sottoinsieme incorporati. Un font sottoinsieme rinumera i glifi che conserva, quindi il content stream della pagina contiene codici CID esadecimali, non i codici carattere originali. I byte sulla pagina non sono le lettere che un essere umano legge, e non sono ricercabili come testo. Una ricerca della didascalia dell'ancora non trova nulla, perché quella didascalia non esiste come testo letterale in nessun punto dello stream

L'unico ancoraggio affidabile per il rettangolo è la geometria che il passaggio di layout ha già prodotto. Lo scostamento e la larghezza misurata di ogni run sono stati calcolati durante il flusso della riga, prima che qualsiasi glifo venisse rinumerato, e descrivono dove il testo apparirà fisicamente. HotPDF prende perciò il rettangolo del collegamento direttamente dal riquadro depositato del run anziché da una ricerca testuale. Poiché la misurazione ha usato il font di rendering, il riquadro è corretto indipendentemente dal subsetting. La geometria sopravvive alla codifica; il testo no. È l'intero argomento a favore del posizionamento per larghezze misurate, ed è la ragione per cui un appiattitore che prova a ricostruire i collegamenti tramite ricerca testuale produce zone sensibili che scivolano o spariscono

Diagramma che mostra perché HotPDF ricava i riquadri sensibili dei link XFA dalla geometria misurata dei run: i font sottoinsieme rinumerano i glifi in codici CID, quindi la ricerca testuale non trova nulla, mentre scostamenti e larghezze del passaggio di layout sopravvivono e danno il Rect corretto
I font sottoinsieme incorporati rinumerano i glifi in codici CID, quindi la ricerca testuale non trova nulla; gli scostamenti e le larghezze misurati dal passaggio di layout sono gli unici ancoraggi che sopravvivono alla codifica

Pilotare l'appiattimento dal vostro codice

Per un PDF che contiene già un pacchetto XFA, il punto di ingresso è FlattenLoadedXFA. Caricate il documento, chiamate il metodo e salvate il risultato. Il parametro Editable decide che ne è dei campi del modulo: passate True per mantenerli come widget AcroForm compilabili, oppure False per marcare ogni widget come di sola lettura e ottenere un record congelato. I blocchi draw di rich text, con i loro run formattati e le annotazioni Link, vengono prodotti in entrambi i casi. La funzione restituisce il conteggio dei widget emessi

var
  Pdf: THotPDF;
  Emitted, i: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('xfa_appeal_form.pdf');
    // True mantiene i campi compilabili; False li congela in sola lettura.
    Emitted := Pdf.FlattenLoadedXFA(True);

    // Ciò che il motore non ha saputo mappare viene segnalato, non sollevato.
    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;

Leggete sempre XFAFlattenWarnings dopo la chiamata. L'elenco viene azzerato all'inizio di ogni appiattimento e accumula una riga per ogni elemento che il motore ha rinunciato a rendere: un tipo di campo non supportato, un'immagine draw che non si è lasciata decodificare, un blocco exData senza span utilizzabili. Nessuno di questi solleva un'eccezione, quindi un elenco di avvisi vuoto è la prova che tutto è stato mappato, e uno non vuoto vi dice esattamente quali originali ispezionare. Quando avete l'XFA grezzo come byte XDP anziché un PDF caricato, il metodo gemello ApplyXFAAsAcroForm prende quei byte direttamente e condivide lo stesso percorso di codice e lo stesso comportamento sugli avvisi. Il metodo complementare AddXFAPacket va nella direzione opposta, incorporando un pacchetto XFA in un documento che state costruendo

Verificare il risultato in un reader

Aprite il file appiattito in Acrobat, o in qualsiasi visualizzatore attuale, e controllate due cose. Primo, che il rich text sia stato reso con la formattazione intatta: i run in grassetto sono in grassetto, i run colorati portano il loro colore, e gli span stanno nell'ordine giusto sulla riga invece di sovrapporsi o uscire dal riquadro. Secondo, che i collegamenti siano attivi. Passate il puntatore su un'ancora e la barra di stato dovrebbe mostrare l'indirizzo di destinazione; cliccatela e l'azione URI dovrebbe aprirlo. Usate l'ispettore di annotazioni del visualizzatore per confermare che ciascuno sia un'autentica annotazione /Link il cui /Rect abbraccia il testo dell'ancora, posata su contenuto che ora è fatto di semplici glifi dipinti anziché di XFA reso a runtime. Quella combinazione, testo statico formattato più vere annotazioni Link sui rettangoli giusti, è ciò che permette al documento appiattito di sopravvivere ai motori XFA di cui non ha più bisogno

L'appiattimento dei campi veri e propri, le caselle di testo, le caselle di spunta e gli elenchi di scelta che circondano questo rich text, è trattato nella nostra guida all'appiattimento dei moduli XFA in widget AcroForm. Per il quadro più ampio sulla costruzione e sul posizionamento manuale delle annotazioni Link, oltre a quelle generate dal percorso di appiattimento, si veda lavorare con le annotazioni PDF in HotPDF. Entrambi poggiano sullo stesso modello di annotazioni e moduli incluso nel componente HotPDF per Delphi per Delphi e C++Builder