Имате шаблон за фактура от трета страна или архивиран договор, създаден преди години със софтуер, който вече никой не може да открие, и задачата е да го направите интерактивен: да поставите поле за подпис в ъгъла, да добавите няколко текстови полета, да превърнете плосък списък за проверка в истински отметки. Уловката е, че не създавате този PDF от нулата. Той вече съществува, вече има страници, потоци със съдържание и шрифтове, върху които нямате контрол, и трябва да добавите AcroForm widget-и към този граф от обекти, без да го изграждате наново. Това е различен проблем от създаването на форма върху нов документ, а частта, която обърква хората, остава невидима, докато не отворите резултата във viewer и полетата, които току-що сте записали, изобщо не се виждат на страницата
HotPDF е роден VCL PDF компонент за Delphi и C++Builder и от v2.247.0 предлага специална група методи точно за това: изграждане на всичките шест стандартни типа полета директно върху документ, зареден с LoadFromFile. Тази статия показва какво правят тези методи, какъв ISO 32000-1 речник изграждат и кой е флагът, без който целият процес тихомълком завършва с празно изглеждащ файл
Защо създаването на полета в зареден документ е отделен кодов път
Когато изграждате PDF от нулата, HotPDF контролира целия обектен модел. Всяка страница е записваем THPDFPage wrapper, а добавянето на текстово поле чрез AddTextField свързва новия widget с анотационния обект на страницата, обекта на страницата и колекцията от полета на формата, а след това генерира appearance stream от шрифтовите ресурси на документа. Appearance stream-ът е видимата повърхност на widget-а, рамката, границата и всеки default text, изрисувани като PDF drawing оператори, които viewer-ът рендерира дословно
Зареденият документ не ви дава нищо от тази опорна конструкция. Страниците са дошли като сурови речници; няма записваем THPDFPage wrapper, върху който да окачите widget, а още по-важно няма pipeline от шрифтови ресурси, който да рисува appearance stream-ове. Затова зареденият път поема по друг маршрут. Той записва речниците на полетата направо в анализирания граф от обекти и адресира страниците по нулево базиран индекс, а не по page object. Типовете полета и битовете на флаговете съвпадат точно с пътя от нулата, така че Text field е Text field и в двата случая; променя се подлежащата plumbing-слой и, което е решаващо, как се изчертава повърхността на widget-а
Флагът /NeedAppearances тук не е по избор
Това е единственият факт, който решава дали работата ви ще се появи. Понеже зареденият път не генерира appearance stream-ове, току-що добавеният widget пристига във viewer-а без /AP запис: поле без описана повърхност. Много viewer-и, помолени да рендерират widget, който няма appearance и няма инструкция да бъде създаден, не рисуват нищо. Полето е във файла, структурно е валидно, достъпно е за tool за попълване на форми и е напълно невидимо за човек
Изходът е дефиниран в ISO 32000-1 §12.7.3: AcroForm речникът носи /NeedAppearances булева стойност, а когато е true conforming reader е длъжен сам да изгради липсващите appearance stream-ове от низa /DA (default appearance) и стойността на всяко поле. HotPDF задава това вместо вас. Първия път, когато добавите поле към зареден документ, EnsureLoadedAcroForm се изпълнява: ако catalog-ът няма /AcroForm той създава такъв, ако няма /Fields масив, той създава и него, и принуждава /NeedAppearances true. Не го извиквате директно, но знанието, че съществува, обяснява поведението. То обяснява и едно важно уточнение за внедряване: шепа минимални или неконформни viewer-и игнорират /NeedAppearances и пак не показват нищо. За масовите читатели флагът върши работа, но ако аудиторията ви използва необичаен embedded renderer, тествайте там, преди да обещаете нещо
Добавяне на шестте типа полета
Всеки метод следва една и съща форма. Подавате нулево базирания индекс на страницата, четирите ъгъла на правоъгълника на widget-а в PDF user-space координати, името на полето и всички допълнителни аргументи, които типът изисква. Правоъгълникът е X1, Y1, X2, Y2 с началото на PDF в долния ляв ъгъл на страницата, така че по-големите Y стойности стоят по-високо; това е координатната конвенция на файловия формат, а не конвенцията на екрана с начало горе вляво, и да ги объркате е втората най-честа грешка след забравянето на флага. Всяко извикване връща нулево базирания индекс на новото поле или -1 ако индексът на страницата е извън обхват или обектът на страницата не може да бъде разрешен
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;
Третият и четвъртият низов аргумент на текстовото поле са името на полето и първоначалната му /V стойност; целочислената стойност е /MaxLen, записвана само когато е по-голяма от нула. HotPDF дава на всяко редактируемо поле низ по подразбиране за appearance stream: /Helv 12 Tf 0 0 0 rg, което е това, което /NeedAppearancesviewer, уважаващ /NeedAppearances, чете, за да реши с какъв шрифт и цвят да изрисува стойността. CheckBox-ът приема export value, низа, който формата изпраща, когато квадратчето е отметнато, плюс булева стойност за началното състояние; вътрешно той записва съответните /V, /AS и /DV имена, така че on/off състоянието да е последователно в момента, в който файлът се отвори. Празна export value по подразбиране става Yes, традиционното „on“ име на отметката
Полета за избор и битовете на /Ff
ComboBox и ListBox са и двата choice field, тип поле /Ch в ISO 32000-1 §12.7.4. Разликата между падащ списък и списък за превъртане е един бит в цялото поле с флагове /Ff: бит 18, Combo флагът, стойност $40000. HotPDF задава този бит за AddLoadedComboBox и го оставя изчистен за AddLoadedListBox; иначе двете са еднакви и и двете приемат опциите си като open array от низове, записан в /Opt запис
// 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');
Две бележки за списъка с опции. HotPDF записва всеки /Opt запис като обикновен низ, при който export value и показваното етикетче са един и същ текст. ISO 32000-1 §12.7.4.4 също допуска двуелементната [export display] форма, когато трябва подадената стойност да се различава от това, което потребителят чете; методите за създаване в зареден документ използват по-простата еднонизова форма, така че ако ви трябват различни export и display стойности, бихте ги задали сами в получилия се речник. А стойността, която подавате като current selection на полето, трябва да е една от предоставените опции, защото viewer-ът я съпоставя със списъка
Натискаемият бутон е другият случай, управляван от флагове: тип поле /Btn с бит 17, флагът PushButton, стойност $10000. Този бит е това, което отделя щракаемия бутон от CheckBox, който също е /Btn поле, но без него. Надписът, който подавате, се записва в речника за appearance characteristics /MK като нормалния надпис /CA. Добре е да сме честни за обхвата тук: бутонът се създава с етикета и правоъгълника си, но методът за създаване в зареден документ не прикачва action, така че сам по себе си това е бутон, който изглежда правилно и не прави нищо при щракване. Закачането на submit, reset или JavaScript actions е отделна задача; от страната на създаването от нулата работният поток поле-плюс-action е описан в изграждане на AcroForm полета и actions в Delphi, което е правилната точка за сравнение с това, което зареденият път нарочно оставя извън обхвата
Речникът, който всяко поле споделя
Под всичките шест метода има един общ builder, който създава widget annotation и го регистрира на две места. Той записва /Type /Annot и /Subtype /Widget, /Rect масива от вашите четири координати, annotation флаговете /F 4 които задават Print бита, така че полето да се появява и на хартия, и на екрана, името на полето /T, типа на полето /FT, флаговете /Ff, и /P обратно препращане към обекта на страницата. После добавя новото поле към /Fields масива на AcroForm и към /Annots масива на тази страница, като по пътя разрешава индиректните препратки, за да разширява реалните масиви, вместо да осиротява widget-а
Това двойно регистриране е важно, защото widget, който живее само в един от двата списъка, е счупен по фин начин. Поле, присъстващо в /Fields но липсващо от /Annots на страницата, е известно на формата, но никога не се рисува; обратният случай се рисува, но е непознат за логиката на формата. HotPDF държи и двата списъка синхронизирани при всяко добавяне, което е онази видима на пръв поглед дребна сметководна работа, която иначе бихте трябвало да уцелите точно ръчно спрямо спецификацията
Няколко честни ограничения
Поставете очакванията предварително, преди да изградите работен поток върху това. Поведението при сплескване и повторно генериране зависи от това viewer-ът да уважава /NeedAppearances, което покрива Acrobat, съвременните PDF engines в браузъра и обичайните desktop readers, но не е твърда гаранция във всеки renderer по света. Ако трябва да произведете файл, чиито полета се рендерират еднакво навсякъде, включително във viewer-и, които игнорират флага, вече сте в territory на appearance stream-овете и по-подходящ е авторският път от нулата, който рисува /AP вместо вас. Signature field-ът също така се създава като празен signature widget, готов за подписване; поставянето на поле не е същото като прилагането на криптографски подпис
Когато променяте нещо вече съществуващо, вместо да добавяте към него, свързаната операция е form flattening, при която вграждате интерактивните полета обратно в статичното съдържание на страницата, така че стойностите да станат постоянни и нередактируеми; този round trip, включително как се обработват forms с XFA, е разгледан в сплескване на XFA и AcroForm полета в Delphi. Добавянето на полета и сплескването на полета са двата края на един и същ lifecycle: тази статия е как добавяте интерактивност към документ, на който му е липсвала, а сплескването е как я махате обратно, след като формата е изпълнила предназначението си
Показаният тук API за формуляри в зареден документ е част от стандартния HotPDF Component за Delphi и C++Builder, заедно с пълната справка за битовете на полетата, обработката на appearance и останалата част от AcroForm модела