HotPDF Delphi Component fyller et eksisterende AcroForm-felt i en lastet PDF gjennom THotPDF.SetFormFieldValue, adressert enten med nullbasert feltindeks eller med fullt kvalifisert feltnavn. Å skrive den nye /V-oppføringen er den lette delen; det som gjør kallet pålitelig på skjemaer fra virkeligheten, er at samme metode også holder tre biter tilstand konsistente som er usynlige til de går galt: den dekodede identiteten til feltet, så et navn med ikke-ASCII-tegn i det hele tatt kan finnes, /AS-utseendetilstanden på avkrysnings- og radioknappwidgeter, og /I-arrayen med valgindekser på valgfelt. Selve den synlige utseendestrømmen er et eget, eksplisitt trinn gjennom EnsureLoadedFieldAppearanceStream
Scenarioet er det hverdagslige: en kunde sender deg sitt eget skjema, en selvangivelse, en forsikringsmelding, en innkjøpsordre noen bygde i Acrobat for år siden, og Delphi-applikasjonen din må fylle det fra en database og levere tilbake en fil som åpner seg riktig overalt. Du har ingen kontroll over hvordan skjemaet ble laget. Feltnavn kan være UTF-16-kodet, eksportverdien til en avkrysningsboks kan være 2 i stedet for Yes, og kombinasjonsbokser kan bruke alternativpar av formen [export display]. Hver av disse detaljene har en regel i ISO 32000-1, og hver regel er noe SetFormFieldValue nå håndterer for deg. Denne artikkelen handler om hva den gjør, hvorfor, og hvor den stopper. For søsterproblemet med å opprette felt som ennå ikke finnes, se å legge til AcroForm-felt i en lastet PDF i Delphi
Hvorfor finner ikke SetFormFieldValue et felt med ikke-ASCII-navn?
Før v2.752.1 var svaret koding: feltet lå i filen under et heksadesimalt UTF-16BE-navn, og navnecachen lagret den heksadesimale stavemåten i stedet for teksten. ISO 32000-1 §12.7.3.1 definerer det delvise feltnavnet /T som en tekststreng, og §7.9.2.2 sier at en tekststreng kan være UTF-16BE med en ledende byteordensmarkør FE FF. Forfatterverktøy serialiserer rutinemessig slike navn som heksstrenger etter §7.3.4.3, så et felt som heter Straße, kommer inn som <FEFF005300740072006100DF0065>. Inne i HotPDF holder THPDFStringObject.Value den rå heksadesimale teksten når IsHexadecimal er satt, som er nøyaktig det du vil ha for en tapsfri rundtur av den opprinnelige ordboken og nøyaktig det du ikke vil ha som oppslagsnøkkel. HPDFLoadedFormTextName skiller de to hensynene. Når relasjonscachen bygges, går hver /T-verdi gjennom den: hvis strengobjektet er heksadesimalt, gjenoppretter HPDFHexToBytes bytessekvensen; hvis bytene begynner med FE FF og har jevn lengde, dekodes lasten som UTF-16BE og kodes om til UTF-8; resultatet føyes deretter sammen med foreldrenavnet sitt med et punktum for å danne det fullt kvalifiserte navnet §12.7.3.1 beskriver, så et barn som heter City under en forelder som heter Address, registreres som Address.City. Cachenøkkelen normaliseres til små bokstaver, noe som gjør at SetFormFieldValue('address.city', ...) også lykkes; det er en bekvemmelighet utover standarden, siden spesifikasjonen behandler navn som skiller mellom store og små bokstaver. Avgjørende er at bare cachenøkkelen endres. /T-objektet i feltordboken beholder sin heksadesimale koding, så lagring av dokumentet skriver ikke om identiteten til et felt du bare fylte ut
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('claim-form.pdf') <= 0 then Exit;
// Kvalifiserte navn dekodes fra UTF-16BE /T-strenger og
// føyes sammen med punktum, så nøstede og ikke-ASCII-navn løses
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Pdf.SetFormFieldValue('Applicant.Straße', 'Hauptstraße 12');
// Verdier som ikke er Latin-1, reiser som FEFF-prefikset UTF-16BE-heks
// og skrives som en PDF-heksstreng
Pdf.SetFormFieldValue('Applicant.City', 'FEFF004D00FC006E006300680065006E');
Pdf.SaveLoadedDocument('claim-form-filled.pdf');
finally
Pdf.Free;
end;
end;
Hva skriver SetFormFieldValue egentlig?
Begge overloadene kjører de samme fem trinnene: finn feltordboken, skriv /V gjennom HPDFSetDictFormValue, avstem valgindeksene, merk ordboken som skitten, avstem knappeutseendetilstandene, og registrer til slutt feltindeksen gjennom NoteLoadedFormFieldDirty. Det siste trinnet betyr noe hvis skjemaet bærer beregningsskript, fordi det skitne settet er det den parameterløse overloaden RecalculateLoadedFormFieldsIncremental konsumerer for å kjøre på nytt bare de beregningene som transitivt leser et endret felt. HPDFSetDictFormValue selv er nøye med objekttypen den erstatter. Hvis den eksisterende /V er et navneobjekt, som er det avkrysnings- og radiofelt bruker som eksportverdi, skrives den nye verdien som et navn, aldri som en streng, fordi PDF-navn er ASCII-only av konstruksjon. Ellers skriver den et strengobjekt og inspiserer verdien du sendte: en streng som begynner med FEFF, har jevn lengde og bare består av heksadesimale sifre, behandles som UTF-16BE-trådformen fra §7.9.2.2 og lagres med IsHexadecimal satt, så den serialiseres som <FEFF...> i stedet for som en literal (FEFF...). Det er mekanismen linjen med City over lener seg på; enhver annen streng lagres som en literal streng med bytene du ga den, så for vanlig latinsk tekst sender du vanlig tekst
Hvorfor beholder en avkrysningsboks den gamle haken etter at verdien endres?
Fordi verdien alene ikke avgjør hva som tegnes for et knappefelt. ISO 32000-1 §12.7.4.2.3 sier at en avkrysningswidget bærer en /AS-utseendetilstand som navngir hvilken strøm i /AP /N som vises akkurat nå, og visningsprogrammer maler fra /AS, ikke fra /V. Hvis du endrer /V til Yes men lar /AS stå på Off, er filen internt selvmotsigende, og flattening baker gjerne det foreldede utseendet for usjekket tilstand inn i siden mens skjemadataene sier avkrysset. ReconcileLoadedButtonAppearanceStates finnes for å lukke det gapet: for et felt der /FT er Btn, besøker den feltordboken selv og hver oppføring i /Kids-arrayen, leser på-tilstandens navn fra /AP /N, og skriver /AS til det navnet når det samsvarer med feltverdien, eller til Off når det ikke gjør det
To detaljer fra virkelige skjemaer formet fiksen i v2.752.3. For det første: en normal utseendeordbok kan inneholde bare på-tilstanden; §12.7.4.2.3 kaller av-utseendet Off, men forfatterverktøy utelater ofte strømmen og lar visningsprogrammet tegne ingenting. Tidligere kode ga opp når ordboken hadde færre enn to oppføringer, så de enkeltilstands-avkrysningsboksene beholdt stille den gamle haken. Sjekken er nå rett og slett at ordboken ikke er tom, og på-tilstandens navn tas som den første nøkkelen som ikke er Off. For det andre: på-tilstandens navn er hva enn forfatteren valgte. Virkelige skjemaer bruker 2, Yes, On eller et lokalisert ord, så sammenligningen skjer mot den faktiske nøkkelen, uten å skille store og små bokstaver, aldri mot en hardkodet Yes. Radioknapper legger til én rynke til, beskrevet i §12.7.4.2.4: valget ligger i /V på foreldrefeltet, mens de enkelte barna eier widgetene og vanligvis ikke har sin egen /V. Hjelperen InheritedButtonValue går derfor oppover /Parent-kjeden, opptil 64 nivåer, til den finner en ikke-tom verdi, så hvert barn sammenlignes med verdien til gruppen det tilhører. Å sette forelderen til ett barns eksportverdi slår på nøyaktig det barnet og av alle søsken
// Avkrysningsboks: eksportverdien må matche på-tilstandens nøkkel i /AP /N
// (ofte 'Yes', men virkelige skjemaer bruker '2', 'On' eller noe annet)
Pdf.SetFormFieldValue('Consent', 'Yes');
// Radiogruppe: /V skrives på forelderen; hver barnewidget får
// /AS satt til sitt eget eksportnavn eller til Off
Pdf.SetFormFieldValue('PaymentMethod', 'Card');
// Tømme en avkrysningsboks: enhver verdi som ikke matcher noen på-tilstand gir /AS Off
Pdf.SetFormFieldValue('Newsletter', 'Off');
Valgfelt: å holde /I i takt med /V
For en kombinasjonsboks eller listeboks er /V ikke det eneste stedet et valg registreres. Tabell 231 i §12.7.4.4 definerer /I som en array av nullbaserte indekser inn i /Opt som identifiserer de valgte elementene, og et visningsprogram som finner /I pekende på alternativ 0 mens /V navngir alternativ 3, kan utheve feil rad. Siden v2.754.1 kjører HPDFReconcileChoiceSelection inne i hvert SetFormFieldValue-kall og bygger /I på nytt fra den nye verdien når den arvede /FT er Ch. Rekkefølgen på operasjonene er bevisst. Den lokale /I-oppføringen slettes først, uten å røre innholdet: hvis den gamle arrayen var et indirekte objekt delt med et annet felt, ville mutering på stedet ødelagt det andre feltets valg, så rutinen dropper referansen og oppretter en fersk direkte array i stedet. Den løser deretter opp /Opt gjennom /Parent-kjeden, siden valgalternativer kan arves, og skanner oppføringene. Et bart strengalternativ sammenlignes direkte; et [export display]-par sammenlignes på eksportelementet, og et par med færre enn to elementer hoppes over. Begge sider går gjennom HPDFLoadedFormTextName, så et heksadesimalt UTF-16-alternativ matcher en heksadesimal UTF-16-verdi uten at du må stave dem identisk. Ved første treff skrives en ett-elements /I, og skanningen stopper; en skalarverdi erstatter alltid et tidligere flervalg, uavhengig av MultiSelect-flagget
Når ingenting matcher, skrives ingen /I i det hele tatt. Det er riktig utfall for en redigerbar kombinasjonsboks, der §12.7.4.4 lar brukeren skrive inn en verdi utenfor alternativlisten; en slik verdi har ingen indeks, og en foreldet indeks ville vært verre enn ingen. Det er også det du får hvis du sender en visningsetikett i stedet for en eksportverdi til en paret alternativliste, så når en kombinasjonsboks nekter å vise valget ditt, sjekk hvilken halvdel av paret du oppga
// /Opt er [[US United States] [CA Canada] [MX Mexico]]:
// match på eksportverdien, og /I blir [1]
Pdf.SetFormFieldValue('Country', 'CA');
// Redigerbar kombinasjonsboks med en verdi utenfor /Opt: /V skrives,
// /I fjernes, og ingen indeks fabrikkeres
Pdf.SetFormFieldValue('Title', 'Principal Engineer');
Verdi og utseende er to separate operasjoner
SetFormFieldValue rører aldri utseendestrømmen til et tekst- eller valgfelt. Etter kallet holder /V den nye teksten mens /AP /N fortsatt maler den gamle, og hvilken av de to et visningsprogram viser, avhenger av om AcroForm-ordboken bærer /NeedAppearances true etter §12.7.3.3 og om visningsprogrammet respekterer det. Hvis du trenger at filen gjengir den nye verdien i hver leser, inkludert flattenere og miniatyrgeneratorer som ignorerer flagget, kall EnsureLoadedFieldAppearanceStream med feltindeksen. Den bygger et Form XObject fra den arvede /DA-strengen, /Q-justeringen, kamoppsettet /MaxLen og verdien, løser opp den navngitte fonten gjennom AcroForm-ressursene /DR så en Type0-font beholder sin egen etterkommerfont i stedet for å degradere til Helvetica, og returnerer True når minst én widget fikk en strøm. Overloaden av SetFormFieldValue som tar navn, gir deg ingen indeks tilbake, så hent en gjennom GetFormField, som returnerer en THPDFLoadedFormField du eier og må frigjøre. Regresjonspakken for endringen i v2.752.1 er eksplisitt på denne delingen: den setter en verdi, kaller EnsureLoadedFieldAppearanceStream, gjengir deretter siden og sjekker at pikslene innenfor widgetrektangelet endret seg mens pikslene utenfor det ikke gjorde det. Å verifisere at /V endret seg, beviser ingenting om hva en bruker vil se
var
Field: THPDFLoadedFormField;
begin
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Field := Pdf.GetFormField('Applicant.FullName');
try
// Mal den nye verdien inn i /AP så visningsprogrammer som
// ignorerer /NeedAppearances fortsatt viser den
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;
Grenser verdt å kjenne før du bygger på dette
ReconcileLoadedButtonAppearanceStates tester den lokale /FT til ordboken du adresserte, så den virker på radioforelderen eller på en avkrysningsboks som bærer sin egen /FT; en barnewidget som adresseres alene, med /FT bare på forelderen, avstemmes ikke gjennom den veien. HPDFReconcileChoiceSelection håndterer en enkelt skalarverdi og skriver høyst én indeks; flervalgs-listebokser med flere valgte oppføringer ligger utenfor det SetFormFieldValue modellerer. Ingen av rutinene validerer verdien du sender mot /Opt eller mot på-tilstandens nøkler, så en skrivefeil gir en Off-avkrysningsboks eller en kombinasjonsboks uten indeks i stedet for et unntak. Og GetFormFieldValue returnerer den lagrede /V-teksten slik den står i ordboken, som for en hekskodet verdi betyr den heksadesimale stavemåten, ikke den dekodede teksten
Når verdiene er inne og utseendene malt, ligger de to naturlige neste stegene på hver sin side av denne operasjonen. Å utveksle feltdata med eksterne systemer i bulk, i stedet for ett SetFormFieldValue-kall av gangen, er det XFDF-import og -eksport i Delphi dekker. Og når det utfylte skjemaet er endelig og ikke lenger skal kunne redigeres, baker flattening av AcroForm- og XFA-felt i Delphi nettopp de /AS-tilstandene og utseendestrømmene som beskrives her, inn i statisk sideinnhold, og det er derfor det ikke er valgfritt å få dem konsistente før flattening
API-et for redigering av lastede skjemaer i denne artikkelen, inkludert SetFormFieldValue, EnsureLoadedFieldAppearanceStream og grafen for inkrementell omberegning, leveres som del av HotPDF Delphi Component for Delphi og C++Builder