Teknisk artikel

Lägg till AcroForm-fält i en inläst PDF i Delphi

Du har en fakturamall från en tredje part, eller ett arkiverat avtal som någon skapade för flera år sedan i programvara som inte längre går att hitta, och kravet är att göra det interaktivt: lägg in en signaturruta i hörnet, några textfält och förvandla en platt checklista till riktiga kryssrutor. Haken är att du inte skapar den här PDF-filen från grunden. Den finns redan, den har redan sidor och innehållsströmmar och typsnitt som du inte kontrollerar, och du måste foga in AcroForm-widgetar i det objektträd som redan finns utan att bygga om det. Det är ett annat problem än att skapa ett formulär i ett nytt dokument, och det som ställer till det för många är osynligt tills du öppnar resultatet i en visare och upptäcker att fälten du just skrev inte syns någonstans på sidan

HotPDF är en inbyggd VCL-PDF-komponent för Delphi och C++Builder, och från och med v2.247.0 exponerar den en särskild metodfamilj just för detta: att bygga alla sex standardfält direkt på ett dokument som har lästs in med LoadFromFile. Den här artikeln går igenom vad de metoderna gör, den ISO 32000-1-dictionary de skapar och den enda flagga utan vilken hela övningen tyst ger en fil som ser tom ut

Varför skapande av fält i inlästa dokument är en egen kodväg

När du bygger en PDF från ingenting äger HotPDF hela objektmodellen. Varje sida är en skrivbar THPDFPage wrapper, och att lägga till ett textfält via AddTextField kopplar in den nya widgeten i sidans annoteringsobjekt, sidobjektet och formulärets fältkollektion, och genererar sedan en utseendeström från dokumentets teckensnittsresurser. Utseendeströmmen är widgetens synliga yta, rutan och ramen och eventuell standardtext, ritad som PDF-ritoperatorer som visaren återger ordagrant

Ett inläst dokument ger dig inget av den här stommen.THPDFPageSidorna kom in som råa ordböcker; det finns ingen skrivbar

Flaggan /NeedAppearances är inte valfri här

Det här är den enda faktorn som avgör om ditt arbete syns. Eftersom den inlästa vägen inte genererar utseendeströmmar kommer en nyligen tillagd widget till visaren utan någon /AP-post: ett fält utan beskriven yta. Många visare som ombeds återge en widget som saknar utseende och instruktion om att bygga ett, ritar ingenting alls. Fältet finns i filen, är strukturellt giltigt, åtkomligt för ett formulärifyllningsverktyg och helt osynligt för en människa

Flykvägen definieras i ISO 32000-1 §12.7.3: AcroForm-dictionaryn innehåller en /NeedAppearances boolesk flagga, och när den är true måste en standardföljande läsare själv konstruera de saknade utseendeströmmarna från varje fälts /DA (default appearance)-sträng och värde. HotPDF sätter detta åt dig. Första gången du lägger till något fält i ett inläst dokument körs EnsureLoadedAcroForm: om katalogen saknar /AcroForm skapar den en, om det inte finns någon /Fields array skapar den en sådan, och den tvingar /NeedAppearances true. Du anropar den inte direkt, men att veta att den finns förklarar beteendet. Det förklarar också en driftsättningsdetalj som är värd att säga rakt ut: ett fåtal minimala eller icke-standardkompatibla visare ignorerar /NeedAppearances och visar ändå ingenting. För vanliga läsare gör flaggan sitt jobb, men om din målgrupp använder en ovanlig inbyggd rendering, testa där innan du lovar något

Att lägga till de sex fälttyperna

Varje metod följer samma form. Du skickar in det nollbaserade sidindexet, de fyra hörnen i widgetens rektangel i PDF:s användarrymdskoordinater, fältnamnet och de extra argument som typen kräver. Rektangeln anges som X1, Y1, X2, Y2 med PDF-ursprunget nere till vänster på sidan, så större Y-värden ligger högre upp; detta är koordinatkonventionen i filformatet, inte skärmens konvention med ursprung uppe till vänster, och att blanda ihop dem är det näst vanligaste felet efter att man glömmer flaggan. Varje anrop returnerar det nya fältets nollbaserade index, eller -1 om sidindexet låg utanför intervallet eller sidobjektet inte gick att lösa

var
  Pdf: THotPDF;
  Idx: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('contract.pdf') <= 0 then Exit;

    // Text field: name, initial value, max length (0 = unlimited)
    Idx := Pdf.AddLoadedTextField(0, 72, 680, 320, 700, 'FullName', '', 0);

    // CheckBox: export value, initial checked state
    Pdf.AddLoadedCheckBox(0, 72, 640, 90, 658, 'AgreeTerms', 'Yes', False);

    // Signature field: just a name and a rectangle
    Pdf.AddLoadedSignatureField(0, 360, 72, 540, 132, 'ApproverSig');

    if Idx >= 0 then
      Pdf.SaveLoadedDocument('contract-interactive.pdf');
  finally
    Pdf.Free;
  end;
end;

Textfältets tredje och fjärde strängargument är fältnamnet och dess initiala /V värde; heltalet är /MaxLen, som bara skrivs när det är större än noll. HotPDF ger varje redigerbart fält en standardutseendesträng på /Helv 12 Tf 0 0 0 rg, vilket är vad en /NeedAppearances-respekterande visare läser för att avgöra vilket typsnitt och vilken färg den ritar värdet i. Kryssrutan tar ett exportvärde, den sträng formuläret skickar när rutan är ikryssad, plus en boolesk för initialt läge; internt skriver den motsvarande /V, /AS och /DV namnposter så att av-/på-läget är konsekvent i samma ögonblick som filen öppnas. Ett tomt exportvärde faller tillbaka till Yes, det konventionella namnet för kryssrutans "på"-tillstånd

Valfält och bitflaggorna i /Ff

ComboBox och ListBox är båda valfält, fälttyp /Ch i ISO 32000-1 §12.7.4. Skillnaden mellan en rullgardinsruta och en rullande lista är en enda bit i heltalet för fältflaggor /Ff: bit 18, Combo-flaggan, värde $40000. HotPDF sätter den biten för AddLoadedComboBox och lämnar den avstängd för AddLoadedListBox; i övrigt är de identiska, och båda tar sina val som en öppen array av strängar som skrivs till posten /Opt-post

// Dropdown (Combo flag set internally) with an initial selection
Pdf.AddLoadedComboBox(0, 72, 600, 300, 620, 'Country', 'Canada',
  ['United States', 'Canada', 'Mexico']);

// Scrolling list, no initial value
Pdf.AddLoadedListBox(0, 72, 520, 300, 590, 'Priority', '',
  ['Low', 'Normal', 'High']);

// Push button with a caption drawn through /MK
Pdf.AddLoadedPushButton(0, 360, 600, 480, 626, 'SubmitBtn', 'Submit');

Två noteringar om alternativlistan. HotPDF skriver varje /Opt-post som en vanlig sträng, där exportvärdet och den visade etiketten är samma text. ISO 32000-1 §12.7.4.4 tillåter också formen med två element [export display] när du behöver att det inskickade värdet ska skilja sig från det användaren läser; metoderna för inläst skapande använder den enklare formen med en enda sträng, så om du behöver olika export- och visningsvärden skulle du sätta dem i den resulterande dictionaryn själv. Och det värde du skickar som fältets aktuella markering bör vara ett av alternativen du gav, eftersom visaren matchar det mot listan

Push-knappen är det andra flaggstyrda fallet: fälttyp /Btn med bit 17, PushButton-flaggan, värde $10000. Den biten är det som skiljer en klickbar knapp från en kryssruta, som också är ett /Btn-fält men utan den. Bildtexten du skickar skrivs in i utseendeegenskapsdictionaryn /MK som den normala bildtexten /CA. Det är värt att vara ärlig om omfattningen här: knappen skapas med sin etikett och rektangel, men metoden för inläst skapande kopplar inte på någon åtgärd, så ensam är det en knapp som ser rätt ut och inte gör någonting när man klickar på den. Att koppla in submit-, reset- eller JavaScript-åtgärder är en separat fråga; för sidan om skapande från grunden täcks arbetsflödet med fält plus åtgärd i bygga AcroForm-fält och åtgärder i Delphi, vilket är rätt jämförelsepunkt för det som den inlästa vägen medvetet lämnar utanför

Dictionaryn som alla fält delar

Under alla sex metoderna finns en gemensam byggare som konstruerar widgetannoteringen och registrerar den på två ställen. Den skriver /Type /Annot och /Subtype /Widget, /Rect arrayen från dina fyra koordinater, annoteringsflaggorna /F 4 som sätter Print-biten så att fältet visas på papper lika väl som på skärm, fältnamnet /T, fälttypen /FT, flaggorna /Ff, och en /P bakåtreferens till sidobjektet. Sedan lägger den till det nya fältet i AcroFormens /Fields array och till den sidans /Annots array, och löser indirekta referenser längs vägen så att den utökar de riktiga arrayerna i stället för att lämna widgeten föräldralös

Den dubbla registreringen spelar roll eftersom en widget som bara lever i en av de två listorna går sönder på ett subtilt sätt. Ett fält som finns i /Fields men saknas i sidans /Annots är känt för formuläret men ritas aldrig; det omvända ritas men är okänt för formulärlogiken. HotPDF håller båda i synk vid varje tillägg, vilket är den typ av administration du annars själv skulle behöva få exakt rätt för hand mot specifikationen

Några ärliga begränsningar

Sätt förväntningarna innan du bygger ett arbetsflöde på det här. Beteendet vid utplattning och återskapande beror på att visaren respekterar /NeedAppearances, vilket täcker Acrobat, moderna PDF-motorer i webbläsare och vanliga skrivbordsvisare, men är ingen hård garanti i alla renderare som finns i verkligheten. Om du måste producera en fil vars fält återges identiskt överallt, även i visare som ignorerar flaggan, är du inne på området för utseendeströmmar och den arbetsflödesväg från grunden som ritar /AP åt dig är bättre lämpad. Signaturfältet skapas likaså som en tom signaturwidget redo att signeras; att placera fältet är inte samma sak som att tillämpa en kryptografisk signatur

För att ändra det som redan finns i stället för att lägga till det är den relaterade åtgärden formulärutplattning, där du bäddar in interaktiva fält tillbaka i statiskt sidinnehåll så att värdena blir permanenta och inte går att redigera; den rundresan, inklusive hur XFA-bärande formulär hanteras, diskuteras i utplattning av XFA- och AcroForm-fält i Delphi. Att lägga till fält och att platta till fält är två ändar av samma livscykel: den här artikeln visar hur du får interaktivitet på ett dokument som saknade det, och utplattning är hur du tar bort den igen när formuläret har tjänat sitt syfte

Det här visade formulär-API:t för inlästa dokument ingår som en del av den ordinarie HotPDF Component för Delphi och C++Builder, tillsammans med hela referensen för fältflaggor, utseendehantering och resten av AcroForm-modellen