HotPDF Delphi Component vyplní existujúce pole AcroForm v načítanom PDF cez THotPDF.SetFormFieldValue, adresované buď zero-based indexom poľa, alebo plne kvalifikovaným názvom poľa. Zapísať nový záznam /V je tá ľahká časť; to, čo robí volanie spoľahlivým na formulároch z reálneho sveta, je fakt, že tá istá metóda drží konzistentné aj tri kusy stavu, ktoré sú neviditeľné, dokým sa nepokazia: dekódovanú identitu poľa, aby sa dalo vôbec nájsť ne-ASCII meno, stav vzhľadu /AS na checkbox a radio widgetoch a pole indexov výberu /I na choice poliach. Viditeľný appearance stream je samostatný, explicitný krok cez EnsureLoadedFieldAppearanceStream
Scenár je ten všedný: zákazník vám pošle svoj vlastný formulár, daňové priznanie, poistnú udalosť, objednávku, ktorú niekto postavil v Acrobate pred rokmi, a vaša Delphi aplikácia ho musí naplniť z databázy a vrátiť súbor, ktorý sa všade otvorí správne. Nemáte žiadnu kontrolu nad tým, ako bol formulár vytvorený. Mená polí môžu byť UTF-16, exportné hodnoty checkboxov môžu byť 2 a nie Yes a combo boxy môžu používať dvojice volieb [export display]. Každý z tých detailov má v ISO 32000-1 svoje pravidlo a každé pravidlo SetFormFieldValue teraz rieši za vás. Tento článok je o tom, čo robí, prečo a kde sa zastaví. K súvisiacemu problému vytvárania polí, ktoré ešte neexistujú, pozri pridávanie polí AcroForm do načítaného PDF v Delphi
Prečo SetFormFieldValue nenájde pole s ne-ASCII názvom?
Pred verziou v2.752.1 bola odpoveďou encoding: pole žilo v súbore pod hexadecimálnym UTF-16BE menom a cache mien ukladala hex zápis namiesto textu. ISO 32000-1 §12.7.3.1 definuje čiastočný názov poľa /T ako textový reťazec a §7.9.2.2 hovorí, že textový reťazec môže byť UTF-16BE s vedúcim byte order markom FE FF. Autorské nástroje také mená bežne serializujú ako hex reťazce podľa §7.3.4.3, takže pole menom Straße dorazí ako <FEFF005300740072006100DF0065>. Vnútri HotPDF drží THPDFStringObject.Value surový hexadecimálny text vždy, keď je nastavené IsHexadecimal, čo je presne to, čo chcete pre bezztrátový round trip pôvodného slovníka, a presne to, čo nechcete ako vyhľadávací kľúč. HPDFLoadedFormTextName oddeľuje tie dve veci. Keď sa stavia cache vzťahov, každá hodnota /T cez ňu prejde: ak je reťazcový objekt hexadecimálny, HPDFHexToBytes obnoví bajtovú sekvenciu; ak bajty začínajú FE FF a majú párnu dĺžku, payload sa dekóduje ako UTF-16BE a prekóduje na UTF-8; výsledok sa potom spojí s názvom rodiča bodkou, aby vzniklo plne kvalifikované meno, ktoré opisuje §12.7.3.1, takže potomok menom City pod rodičom menom Address je registrovaný ako Address.City. Kľúč v cache sa normalizuje na malé písmená, čím prejde aj SetFormFieldValue('address.city', ...); to je pohodlie nad rámec štandardu, keďže špecifikácia berie mená ako case-sensitive. Kľúčové je, že sa mení len kľúč v cache. Objekt /T v slovníku poľa si drží svoje hexadecimálne kódovanie, takže uloženie dokumentu neprepíše identitu poľa, ktoré ste len vyplnili
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('claim-form.pdf') <= 0 then Exit;
// Kvalifikované mená sa dekódujú z UTF-16BE reťazcov /T a
// spájajú bodkami, takže vnorené aj ne-ASCII mená sa rozložia
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Pdf.SetFormFieldValue('Applicant.Straße', 'Hauptstraße 12');
// Hodnoty, ktoré nie sú Latin-1, cestujú ako UTF-16BE hex s prefixom FEFF
// a zapisujú sa ako PDF hexadecimal string
Pdf.SetFormFieldValue('Applicant.City', 'FEFF004D00FC006E006300680065006E');
Pdf.SaveLoadedDocument('claim-form-filled.pdf');
finally
Pdf.Free;
end;
end;
Čo SetFormFieldValue vlastne zapisuje?
Obe prekrytia robia tých istých päť krokov: lokalizuj slovník poľa, zapíš /V cez HPDFSetDictFormValue, zosúlaď indexy výberu pri choice poliach, označ slovník ako dirty, zosúlaď stavy vzhľadu tlačidiel a nakoniec zaznamenaj index poľa cez NoteLoadedFormFieldDirty. Ten posledný krok je dôležitý, ak formulár nesie výpočtové skripty, pretože práve množinu dirty konzumuje prekrytie RecalculateLoadedFormFieldsIncremental bez parametrov, aby znovu spustilo len tie výpočty, ktoré tranzitívne čítajú zmenené pole. Samotné HPDFSetDictFormValue si dáva pozor na typ objektu, ktorý nahrádza. Ak je existujúce /V name objekt, čo checkbox a radio polia používajú pre svoju exportnú hodnotu, nová hodnota sa zapíše ako name, nikdy nie ako reťazec, pretože PDF mená sú od svojej podstaty len ASCII. Inak zapíše reťazcový objekt a preskúma hodnotu, ktorú ste odovzdali: reťazec, ktorý začína FEFF, má párnu dĺžku a pozostáva výhradne z hex číslic, sa berie ako drôtová forma UTF-16BE z §7.9.2.2 a uloží sa s nastaveným IsHexadecimal, takže sa serializuje ako <FEFF...> a nie ako literál (FEFF...). Na tom mechanizme stojí riadok s City vyššie; každý iný reťazec sa uloží ako literál s bajtmi, ktoré ste dali, takže pre bežný latinský text odovzdávate bežný text
Prečo si checkbox po zmene hodnoty drží starý krížik?
Pretože pri tlačidlovom poli hodnota sama nerozhoduje o tom, čo sa vykreslí. ISO 32000-1 §12.7.4.2.3 určuje, že checkbox widget nesie stav vzhľadu /AS, ktorý menuje, ktorý stream v /AP /N je práve zobrazený, a viewery kreslia z /AS, nie z /V. Ak zmeníte /V na Yes, ale /AS necháte na Off, súbor je vnútorne protirečivý a flattening ochotne zapečie ten zastaraný nezaškrtnutý vzhľad do stránky, kým údaje formulára hovoria, že je zaškrtnuté. ReconcileLoadedButtonAppearanceStates existuje, aby tú medzeru zavrel: pri poli, ktorého /FT je Btn, navštívi samotný slovník poľa aj každý záznam v jeho poli /Kids, prečíta názov on-stavu z /AP /N a prepíše /AS na ten názov, keď sa zhoduje s hodnotou poľa, alebo na Off, keď sa nezhoduje
Opravu vo v2.752.3 formovali dva detaily z reálnych formulárov. Po prvé, normálny appearance slovník smie obsahovať len on stav; §12.7.4.2.3 menuje off vzhľad ako Off, ale autorské nástroje jeho stream často vynechávajú a nechávajú viewera nekresliť nič. Starší kód sa vzdal, keď slovník držal menej než dva záznamy, takže tie jednostavové checkboxy potichu držali starý krížik. Kontrola je teraz jednoducho tá, že slovník nie je prázdny, a názov on-stavu sa berie ako prvý kľúč, ktorý nie je Off. Po druhé, názov on-stavu je akýkoľvek, aký autor zvolil. Reálne formuláre používajú 2, Yes, On alebo lokalizované slovo, takže porovnanie je proti skutočnému kľúču, bez ohľadu na veľkosť písmen, nikdy nie proti natvrdo zapísanému Yes. Radio tlačidlá pridávajú ešte jednu záhyb, opísanú v §12.7.4.2.4: výber žije v /V na rodičovskom poli, kým jednotliví potomkovia vlastnia widgety a zvyčajne nemajú vlastné /V. Vnorený helper InheritedButtonValue preto kráča nahor po reťazci /Parent, až 64 úrovní, dokým nenájde neprázdnu hodnotu, takže každý potomok sa porovnáva proti hodnote skupiny, do ktorej patrí. Nastavenie rodiča na exportnú hodnotu jedného potomka zapne presne toho potomka a každého súrodenca vypne
// Checkbox: exportná hodnota musí sedieť s kľúčom on-stavu v /AP /N
// (často 'Yes', ale reálne formuláre používajú '2', 'On' alebo čokoľvek iné)
Pdf.SetFormFieldValue('Consent', 'Yes');
// Radio skupina: /V sa zapisuje na rodičovi; každý potomok widget dostane
// /AS nastavené na svoj vlastný exportný názov alebo na Off
Pdf.SetFormFieldValue('PaymentMethod', 'Card');
// Vyčistenie checkboxu: hodnota, ktorá nesedí so žiadnym on stavom, dá /AS Off
Pdf.SetFormFieldValue('Newsletter', 'Off');
Choice polia: /I drží krok s /V
Pri combo boxe alebo list boxe nie je /V jediné miesto, kde je zaznamenaný výber. Tabuľka 231 v §12.7.4.4 definuje /I ako pole zero-based indexov do /Opt, ktoré identifikuje vybrané položky, a viewer, ktorý nájde /I ukazujúce na voľbu 0, kým /V menuje voľbu 3, môže zvýrazniť nesprávny riadok. Od verzie v2.754.1 beží HPDFReconcileChoiceSelection vnútri každého volania SetFormFieldValue a keď je dedené /FT rovné Ch, prebuduje /I z novej hodnoty. Poradie operácií je zámerné. Lokálny záznam /I sa najprv zmaže, bez dotyku na jeho obsah: ak by staré pole bolo nepriamym objektom zdieľaným s iným poľom, mutovanie na mieste by pokazilo výber toho druhého poľa, takže rutina referenciu zahodí a namiesto nej vytvorí čerstvé priame pole. Potom rozloží /Opt cez reťazec /Parent, keďže voľby choice polí môžu byť dedené, a preskenuje záznamy. Holá reťazcová voľba sa porovnáva priamo; dvojica [export display] sa porovnáva na svojom exportnom elemente a dvojica s menej než dvoma elementmi sa preskočí. Obe strany prechádzajú cez HPDFLoadedFormTextName, takže hex UTF-16 voľba sedí s hex UTF-16 hodnotou bez toho, aby ste ich museli napísať identicky. Pri prvom zhode sa zapíše jednoprvkové /I a sken sa zastaví; skalárna hodnota vždy nahradí akýkoľvek predchádzajúci multi-výber, bez ohľadu na príznak MultiSelect
Keď nič nesedí, nezapíše sa žiadne /I. To je správny výsledok pre editovateľný combo box, kde §12.7.4.4 dovoľuje používateľovi napísať hodnotu mimo zoznamu volieb; taká hodnota nemá index a zastaraný index by bol horší než žiadny. Je to aj to, čo dostanete, ak do párového zoznamu volieb odovzdáte display label namiesto exportnej hodnoty, takže keď combo box odmieta zobraziť váš výber, skontrolujte, ktorú polovicu dvojice ste dodali
// /Opt je [[US United States] [CA Canada] [MX Mexico]]:
// páruj na exportnú hodnotu a /I sa stane [1]
Pdf.SetFormFieldValue('Country', 'CA');
// Editovateľné combo s hodnotou mimo /Opt: /V sa zapíše,
// /I sa odstráni a žiadny index sa nevymyslí
Pdf.SetFormFieldValue('Title', 'Principal Engineer');
Hodnota a vzhľad sú dve samostatné operácie
SetFormFieldValue sa nikdy nedotkne appearance streamu textového poľa ani choice poľa. Po volaní drží /V nový text, kým /AP /N stále kreslí ten starý, a ktorý z tých dvoch viewer ukáže, závisí od toho, či AcroForm slovník nesie /NeedAppearances true podľa §12.7.3.3 a či to viewer rešpektuje. Ak potrebujete, aby súbor vykreslil novú hodnotu v každej čítačke, vrátane flattenerov a generátorov miniatúr, ktoré ten príznak ignorujú, zavolajte EnsureLoadedFieldAppearanceStream s indexom poľa. Ten postaví Form XObject z dedeného reťazca /DA, zarovnania /Q, comb rozloženia /MaxLen a hodnoty, rozloží menovaný font cez zdroje /DR v AcroForm, aby si Type0 font podržal svoj vlastný descendant font a nedegradoval na Helvetica, a vráti True, keď aspoň jeden widget dostal stream. Prekrytie SetFormFieldValue podľa mena vám index nevráti, takže si ho vyzdvihnite cez GetFormField, ktoré vracia THPDFLoadedFormField, ktorý vlastníte a musíte uvoľniť. Regresná sada pre zmenu vo v2.752.1 je o tomto rozdelení explicitná: nastaví hodnotu, zavolá EnsureLoadedFieldAppearanceStream, potom vykreslí stránku a skontroluje, že sa pixely vnútri obdĺžnika widgetu zmenili, kým pixely mimo neho nie. Overiť, že sa /V zmenilo, nedokazuje nič o tom, čo používateľ uvidí
var
Field: THPDFLoadedFormField;
begin
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Field := Pdf.GetFormField('Applicant.FullName');
try
// Nakresli novú hodnotu do /AP, aby ju viewery ignorujúce
// /NeedAppearances stále zobrazili
if not Pdf.EnsureLoadedFieldAppearanceStream(Field.Index) then
raise Exception.Create('No widget rectangle to paint into');
finally
Field.Free;
end;
Pdf.SaveLoadedDocument('claim-form-filled.pdf');
end;
Limity, ktoré sa hodí poznať, než na tomto postavíte
ReconcileLoadedButtonAppearanceStates testuje lokálne /FT toho slovníka, ktorý ste adresovali, takže pôsobí na radio rodiča alebo na checkbox, ktorý nesie vlastné /FT; potomok widget adresovaný samostatne, s /FT len na svojom rodičovi, sa tou cestou nezosúlaďuje. HPDFReconcileChoiceSelection spracúva jednu skalárnu hodnotu a zapíše nanajvýš jeden index; multi-výberové list boxy s niekoľkými zvolenými záznamami sú mimo toho, čo SetFormFieldValue modeluje. Ani jedna rutina nevaliduje hodnotu, ktorú odovzdáte, proti /Opt ani proti kľúčom on-stavov, takže preklep vyprodukuje Off checkbox alebo combo bez indexu namiesto výnimky. A GetFormFieldValue vracia text /V tak, ako sedí v slovníku, čo pri hexadecimálne kódovanej hodnote znamená hexadecimálny zápis, nie dekódovaný text
Keď sú hodnoty vnútri a vzhľady nakreslené, dva prirodzené ďalšie kroky sedia na oboch stranách tejto operácie. Výmenu údajov polí s externými systémami hromadne, a nie jedno volanie SetFormFieldValue za druhým, pokrýva import a export XFDF v Delphi. A keď je vyplnený formulár hotový a nemá už byť editovateľný, flattening polí AcroForm a XFA v Delphi zapečie presne tie stavy /AS a appearance streamy opísané vyššie do statického obsahu stránky, a preto ich zosúladenie pred flatteningom nie je voliteľné
API na editáciu načítaných formulárov z tohto článku, vrátane SetFormFieldValue, EnsureLoadedFieldAppearanceStream a grafu inkrementálneho prepočtu, je súčasťou HotPDF Delphi Component pre Delphi a C++Builder