Una fattura o una nota spese in PDF in cui le righe si sommano da sole e la data si formatta da sé ha bisogno di tre cose innestate sul suo AcroForm, e HotPDF le aggiunge tutte e tre a un file caricato in Delphi: ApplyLoadedFieldCalculation per i totali con AFSimple_Calculate, ApplyLoadedFieldValidation per gli helper Acrobat AF di formato e convalida, e EnsureLoadedFieldAppearanceStream per fissare il valore che un visualizzatore disegna davvero
HotPDF è un componente PDF VCL nativo per Delphi e C++Builder, e questi tre metodi operano su un documento aperto con LoadFromFile, non su uno che state costruendo da zero. Ciascuno riceve un indice di campo a base zero nell'array /Fields dell'AcroForm, lo stesso ordine restituito da GetLoadedFormFieldNames, e scrive direttamente nel dizionario del campo, quindi il lavoro completo è caricare, indicizzare, agganciare, SaveLoadedDocument. Costruire l'insieme dei campi è invece un compito a parte, trattato in aggiungere campi AcroForm a un PDF caricato in Delphi, mentre questa pagina parla di dare aritmetica e formattazione a un insieme di campi già esistente
Perché un modulo PDF ha bisogno di script per sommare i propri campi?
Perché il PDF non ha uno strato foglio di calcolo. Un campo di testo contiene una stringa, non una formula, e nulla nel file ricalcola un totale quando una riga cambia se non c'è uno script agganciato che lo faccia. Il meccanismo è il dizionario delle azioni aggiuntive del campo, /AA (ISO 32000-1 §12.7.5), che associa eventi scatenanti ad azioni: un trigger di battitura (K) mentre arrivano i caratteri, un trigger di formato per la visualizzazione, un trigger di convalida (V) alla conferma e un trigger di calcolo (C) che scatta ogni volta che un qualsiasi campo del modulo cambia. Ogni azione è una azione JavaScript, /S /JavaScript (ISO 32000-1 §12.6.4.14), e lo script che esegue è ciò che davvero compie la somma o la riformattazione. Acrobat porta con sé una libreria di helper già pronti, AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx e gli altri, così lo script agganciato è di solito una chiamata di una riga a quella libreria anziché codice su misura
Un dettaglio decide se un modulo a più passi dà la risposta giusta: l'ordine di calcolo. Acrobat esegue ogni campo che porta una azione di calcolo, e li esegue nella sequenza elencata dall'array /CO dell'AcroForm, non nell'ordine di pagina. ApplyLoadedFieldCalculation aggancia l'azione di calcolo al campo che nominate; quando un campo calcolato alimenta un altro campo, un subtotale letto da una riga di imposta e poi un totale generale che legge entrambi, dovete confermare che l'ordine in /CO elenchi ciascun ingresso prima del campo che lo consuma, altrimenti il totale generale leggerà un subtotale obsoleto e resterà indietro di una modifica
Come si fa a far calcolare i totali automaticamente a un modulo PDF?
ApplyLoadedFieldCalculation riceve l'indice del campo di destinazione, una operazione e i nomi dei campi sorgente da aggregare. HotPDF scrive AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) nell'azione di calcolo del campo di destinazione, e l'enumerazione THPDFFieldCalcOp sceglie la funzione: fccSum, fccAverage, fccProduct, fccMinimum e fccMaximum corrispondono a SUM, AVG, PRD, MIN e MAX di Acrobat. I nomi sorgente sono nomi di campo AcroForm, le stesse stringhe che GetLoadedFormFieldNames restituisce, quindi un totale su tre righe di dettaglio e una voce di spedizione è una sola chiamata
var
Pdf: THotPDF;
Names: THPDFAnsiStringArray;
i, TotalIdx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('invoice.pdf');
Names := Pdf.GetLoadedFormFieldNames;
TotalIdx := -1;
for i := 0 to High(Names) do
if Names[i] = 'grandTotal' then TotalIdx := i;
if TotalIdx >= 0 then
begin
Pdf.ApplyLoadedFieldCalculation(TotalIdx, fccSum,
['lineTotal.1', 'lineTotal.2', 'lineTotal.3', 'shipping']);
Pdf.EnsureLoadedFieldAppearanceStream(TotalIdx);
end;
Pdf.SaveLoadedDocument('invoice-live.pdf');
finally
Pdf.Free;
end;
end;
La chiamata a EnsureLoadedFieldAppearanceStream sul totale non è cosmetica. Il calcolo gira dentro il visualizzatore, quindi finché un lettore capace di eseguire script non apre il file il campo del totale non ha alcun contenuto disegnato proprio. Fissare uno stream di aspetto gli dà nel frattempo una cifra da mostrare, ed è quella che un lavoro di stampa o un generatore di miniature raccoglierà quando non esegue mai lo script
Formattare e convalidare i campi con la suite di helper AF
ApplyLoadedFieldValidation aggancia in una sola chiamata gli script di formato, battitura e convalida, e il valore THPDFFieldValidationKind che passate decide quale helper Acrobat viene collegato. Passate fvkCurrency e HotPDF scrive AFNumber_Format(2,0,0,0,"$ ",true) come script di formato con il corrispondente AFNumber_Keystroke; fvkNumber usa "." come argomento separatore, fvkPercentage emette AFPercent_Format, e fvkDate emette AFDate_FormatEx("dd/mm/yyyy") a meno che non passiate una vostra maschera nell'argomento DateFormat. I formati speciali, fvkZipCode, fvkSSN e fvkPhone, corrispondono a AFSpecial_Format con il codice che Acrobat riserva a ciascuno, mentre fvkArbitraryMask accetta una stringa di maschera tramite l'argomento Mask per tutto ciò che quelle preimpostazioni non coprono. Per i tipi numerici HotPDF aggancia anche uno script AFRange_Validate, così un valore fuori intervallo viene rifiutato alla conferma
// Data della fattura in ISO, importo come valuta e maschera per il codice fiscale
Pdf.ApplyLoadedFieldValidation(DateIdx, fvkDate, 'yyyy-mm-dd', '');
Pdf.ApplyLoadedFieldValidation(AmountIdx, fvkCurrency, '', '');
Pdf.ApplyLoadedFieldValidation(TaxIdIdx, fvkArbitraryMask, '', '999-99-9999');
Pdf.EnsureLoadedFieldAppearanceStream(AmountIdx);
Gli helper AF coprono le forme più comuni, così non dovete mai scrivere JavaScript a mano per esse. Quando un campo richiede una logica che nessuna preimpostazione esprime, un checksum, una regola fra più campi, una espressione regolare su misura, agganciate voi stessi lo script grezzo con i metodi di azione a livello di campo descritti in costruire campi e azioni AcroForm con HotPDF, che accettano direttamente una stringa JavaScript. Entrambe le vie scrivono nello stesso dizionario /AA, quindi un formato valuta preimpostato su un campo e una convalida scritta a mano su un altro convivono nello stesso documento
Perché bisogna rigenerare lo stream di aspetto?
Perché nel PDF la stringa del valore e i pixel che un visualizzatore disegna sono due cose diverse, e cambiare una non cambia l'altra. Un campo conserva il proprio valore in /V, ma ciò che un lettore rende proviene dallo stream di aspetto del campo, /AP /N. Modificate /V attraverso il grafo degli oggetti e un visualizzatore rigoroso mostrerà ancora il vecchio aspetto finché qualcosa non lo rigenera. Il dizionario del modulo porta un flag /NeedAppearances che chiede al visualizzatore di ricostruire ogni aspetto all'apertura, e Acrobat lo rispetta, ma i renderer incorporati nei browser, i lettori mobili, i server di stampa e i generatori di miniature spesso lo ignorano e disegnano lo stream già presente, oppure nulla del tutto. EnsureLoadedFieldAppearanceStream colma quella lacuna costruendo esso stesso il Form XObject /AP /N, a partire da /DA, /V e /Rect del campo, così il valore disegnato viaggia dentro il file invece di dipendere dal visualizzatore che lo ridisegna
// Fissa un aspetto per ogni campo prima del salvataggio, così i valori si vedono ovunque
Names := Pdf.GetLoadedFormFieldNames;
for i := 0 to High(Names) do
Pdf.EnsureLoadedFieldAppearanceStream(i);
Pdf.SaveLoadedDocument('invoice-flat-appearance.pdf');
Dove gli script AF smettono di funzionare
Ogni script AF* dipende dal fatto che il visualizzatore abbia un motore JavaScript, e la maggior parte dei visualizzatori non ce l'ha. Acrobat e una manciata di prodotti desktop eseguono questi helper; i visualizzatori PDF integrati in Chrome, Edge, Firefox e nelle principali piattaforme mobili, insieme ai rasterizzatori lato server e alle catene di stampa, non eseguono alcun JavaScript di modulo. In quei lettori il calcolo non scatta mai e il formato non viene mai applicato: il campo mostra semplicemente lo stream di aspetto con cui è stato salvato l'ultima volta, ed è esattamente per questo che fissare /AP /N conta. Trattate lo strato AF* come una comodità per la parte di utenti che usa un lettore capace di eseguire script, mai come una garanzia. Qualsiasi totale che debba essere corretto sul server, un importo di fattura che addebiterete, una cifra di imposta che dichiarerete, va ricalcolato dalla vostra parte quando il modulo torna indietro, perché non potete dare per scontato che il lettore che lo ha compilato abbia eseguito una sola riga del vostro script
Vale la pena nominare un altro caso: i moduli arrivati come XFA. Un modulo XFA dinamico porta il proprio motore di calcolo in un payload XML di cui gli helper AF* non sanno nulla, quindi agganciare AFSimple_Calculate a un campo XFA non ottiene nulla. Appiattite prima il modulo XFA in un semplice AcroForm, come descritto in appiattire XFA in AcroForm in Delphi, e i tre metodi visti qui si applicheranno ai campi AcroForm risultanti esattamente come a qualsiasi altro modulo caricato
Gli AcroForm che si calcolano e si formattano da soli si riducono a tre chiamate su un documento caricato, agganciare il calcolo, agganciare formato e convalida, fissare l'aspetto, seguite da SaveLoadedDocument. L'API per i moduli caricati mostrata qui fa parte del HotPDF Delphi Component per Delphi e C++Builder, che documenta per intero le enumerazioni THPDFFieldValidationKind e THPDFFieldCalcOp insieme al resto della superficie AcroForm