Само по себе си полето във форма на PDF е просто кутия, която съдържа стойност. Това, което кара една форма да се държи като малко приложение, е действието, прикрепено към нея: щракване, което скрива секция, извлича записани стойности от файл, преминава към последната страница или изпълнява скрипт, който сумира колона. Нищо от това не живее в самото поле. То се намира в речник с действия (action dictionary), а ISO 32000-1 организира цялото семейство в §12.6. Тази статия разглежда действията, към които Delphi програма посяга най-често, и показва как PDF Library for Delphi свързва всяко едно от тях с поле или връзка
Мисленният модел, който си струва да запомните, е че полето и действието са отделни обекти, свързани чрез препратка. Анотация на графичен компонент (widget annotation) или анотация на връзка (link annotation) носи действие в своя запис /A. Действието посочва полето, върху което работи, по име (title), а не по индекс, така че името, което давате на полето, е манипулаторът, който всяко следващо действие използва, за да го намери. След като това разделение е ясно, API спира да изглежда как съвкупност от произволни извиквания и започва да изглежда как един шаблон, приложен към четири вида глаголи
Именувани действия: навигация без номер на страница
Най-простите действия изобщо не носят параметри. ISO 32000-1 §12.6.4.11, Таблица 194, дефинира именувани действия: четецът интерпретира символно име по време на изпълнение, вместо да следва съхранена дестинация. Универсално се поддържат четири имена и те са точно тези, които читателят очаква от лентата с инструменти: NextPage, PrevPage, FirstPage и LastPage. Тъй като дестинацията е относителна спрямо страницата, която четецът показва в момента, бутонът "Следващ" (Next), създаден по този начин, работи на всяка страница, без да е необходимо да изчислявате цел
В PDF Library for Delphi именуваното действие е прикрепено към активен правоъгълник (hotspot rectangle) на текущата страница. Четвъртият и петият целочислени аргументи избират глагола и външния вид
// NamedActionType: 0 = NextPage, 1 = PrevPage, 2 = FirstPage, 3 = LastPage
// Бит 0 на Options (стойност 1) изчертава рамка около горещата зона
Pdf.AddLinkToNamedAction(500, 560, 60, 18, 0, 1); // Next
Pdf.AddLinkToNamedAction(40, 560, 60, 18, 1, 1); // Previous
Pdf.AddLinkToNamedAction(110, 560, 60, 18, 3, 1); // преминава към последната страница
Няма дестинация, която да поддържате в синхрон, което е основната цел. Именуваното действие оцелява при вмъкване и изтриване на страници, защото никога не назовава конкретна страница. Сравнете това с изрична връзка за преминаване (go-to link), която съхранява индекс на целевата страница, който трябва да преномерирате в момента, в който документът нарасне
Действието Hide и клопката с масива
Действието Hide, ISO 32000-1 §12.6.4.10, Таблица 196, превключва видимостта на едно или повече полета. Това е най-чистият начин за изграждане на поведение за показване и скриване без скриптове и е точно това, което искате за връзка "Покажи детайли" или за два взаимно изключващи се панела, където разкриването на единия скрива другия. Действието носи цел в своя запис /T и булева стойност /H, която решава посоката: скриване, когато е истина (true), показване, когато е лъжа (false)
Тънкостта е изцяло в това как се кодира тази цел и това е вид детайл, който създава форма, работеща на вашата машина, но неуспешна на машината на клиента. Когато действието посочва едно поле, /T се записва като един текстов низ. Когато посочва няколко, /T се записва като масив от текстови низове. По-старите четци не третират масив с един елемент по същия начин, по който третират чист низ, така че кодирането трябва да се разклонява според броя: едно единствено име трябва да се изведе като низ, а не като масив с дължина едно, за да бъде уважено от възможно най-широк спектър четци. PDF Library for Delphi взема това решение вместо вас. Вие предавате имена на полета, разделени с запетаи, точки и запетаи или прекъсвания на редове, а модулът за запис извежда единичен низ за едно име и масив за две или повече
// HideFlag, различно от нула, скрива изброените полета (/H true); нула ги показва
// Едно име -> /T е текстов низ. Две или повече -> /T е масив от низове
Pdf.AddLinkToHideField(40, 700, 90, 18, 'ShippingAddress', 1, 1);
Pdf.AddLinkToHideField(140, 700, 90, 18,
'ShippingName,ShippingAddress,ShippingZip', 1, 1);
Тъй като действието не реферира към външен ресурс, то остава съвместимо с PDF/A. Имената, които предавате, са напълно квалифицирани имена на полета, поради което дъщерно поле в група трябва да бъде адресирано чрез неговия пълен път с точки, а не просто с неговото собствено име
ImportData: предварително попълване от FDF
Където действието Hide пренарежда това, което вече е на страницата, действието за импортиране на данни (import-data action) внася стойности отвън. ISO 32000-1 §12.6.4.8, Таблица 198, го дефинира като действие, което попълва AcroForm от файл с формат за данни на форми (Forms Data Format - FDF) на диска. Това е действието зад контролите "Презареждане на примерни данни" (Reload sample data) или "Възстановяване по подразбиране" (Reset to defaults), при които FDF файлът се доставя заедно с PDF файла и съдържа каноничните стойности на полетата. Извикването отразява останалите, като приема правоъгълника на активната зона, пътя до FDF и битова маска за външен вид: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Файлът не е необходимо да съществува при създаването на PDF файла, но трябва да присъства, когато потребителят щракне, а всички обратни наклонени черти в пътя се пренаписват в каноничната за PDF наклонена черта
Едно ограничение си струва да се отбележи ясно, защото често е изненада. Действието за импортиране на данни сочи към външен файл, така че не е разрешено в PDF/A. Когато документът е в режим PDF/A, извикването връща нула и не добавя нищо, вместо да произведе файл, който не преминава валидация. Ако вашият конвейер е насочен към архивен изход, предварителното попълване трябва да се случи по време на генерирането чрез директно записване на стойностите на полетата, а не чрез отлагането им за щракване
JavaScript: глобални пакети и скриптове за отделни действия
За логика, която надхвърля показването, скриването и импортирането, семейството от действия посяга към JavaScript на ниво документ. Има две различни места, където може да живее един скрипт, и разликата е важна. Пакетът JavaScript на ниво документ се съхранява веднъж за целия файл и се изпълнява при отваряне на документа, което го прави подходящото място за дефиниции на функции и споделено състояние. Скриптът за конкретно действие е прикачен към една връзка или поле и се изпълнява само когато този обект се активира, което го прави подходящото място за единия ред, който извиква функция, вече дефинирана от пакета
PDF Library for Delphi разкрива и двете. AddGlobalJavaScript съхранява именуван пакет на ниво документ; повторното използване на име замества всичко, което е било съхранено под него. AddLinkToJavaScript прикачва скрипт към активна зона, така че щракването да го изпълни
// Пакет на ниво документ: дефинирайте функцията за повторна употреба веднъж
Pdf.AddGlobalJavaScript('Totals',
'function recalcTotal() {' +
' var net = this.getField("Net").value;' +
' var tax = this.getField("Tax").value;' +
' this.getField("Gross").value = Number(net) + Number(tax);' +
'}');
// Скрипт за конкретното действие върху връзка: просто извикайте споделената функция
Pdf.AddLinkToJavaScript(40, 620, 100, 18, 'recalcTotal();', 1);
Запазването на функцията в глобалния пакет и извикването във връзката не е въпрос на стилово предпочитание. Това избягва дублирането на едно и също тяло на всяка контрола, която се нуждае от него, и означава, че четец с деактивирани скриптове просто няма да направи нищо при щракване, вместо да блокира при лошо форматиран вграден блок. Освен това поддържа записите за отделните действия малки, което поддържа файла четим, когато го инспектирате по-късно
Полета, дъщерни полета и замразяване на резултата
Действията се нуждаят от полета, върху които да действат, така че е полезно да се види как се създава едно поле. NewFormField създава поле на текущата страница и връща неговия индекс; целочисленият тип избира вида, където 1 е Text, 2 е Pushbutton, 3 е Checkbox, 4 е Radiobutton, 5 е Choice, 6 е Signature и 7 е Parent (родител), който притежава дъщерни полета, но сам по себе си не рисува нищо. Името (title), което предавате, не може да съдържа точка, тъй като точката е разделител в напълно квалифицираните имена, които действията използват за адресиране на дъщерни полета
Радио групите и йерархичните форми се изграждат чрез даване на дъщерни полета на родителско поле. NewChildFormField добавя дъщерно поле под наименован родител, а за случаите на радио бутони и избори AddFormFieldSub добавя индивидуалните опции и връща временен индекс, който използвате за позициониране на всяка една. Когато интерактивната фаза приключи и искате да замразите дадено поле, така че текущият му външен вид да стане постоянно съдържание на страницата, FlattenFormField изрисува полето върху страницата и го премахва от формата. След изглаждане (flatten) индексите на следващите полета се изместват надолу с едно, което е единственото нещо, което трябва да запомните, ако изглаждате няколко полета в цикъл
var
Pdf: TPDFlib;
FldShip: Integer;
begin
Pdf := TPDFlib.Create;
try
Pdf.SetOrigin(1); // top-left origin
Pdf.SetPageSize('A4');
Pdf.NewPage;
// Текстово поле, към което действието Hide ще се насочи по заглавието му
FldShip := Pdf.NewFormField('ShippingAddress', 1);
Pdf.SetFormFieldBounds(FldShip, 40, 120, 240, 20);
Pdf.SetFormFieldValue(FldShip, '');
// Свържете връзка Hide и навигационна връзка към тази страница
Pdf.DrawText(40, 110, 'Toggle shipping block:');
Pdf.AddLinkToHideField(220, 100, 70, 16, 'ShippingAddress', 1, 1);
Pdf.AddLinkToNamedAction(500, 800, 60, 18, 3, 1); // Last page
// Скрипт на ниво документ, достъпен за всяко събитие във файла
Pdf.AddGlobalJavaScript('OnOpen',
'app.alert("Form ready", 3);');
// Фиксирайте полето, ако изходът вече не трябва да може да се редактира
// Pdf.FlattenFormField(FldShip);
if Pdf.SaveToFile('form_actions.pdf') <> 1 then
raise Exception.Create('Save failed');
finally
Pdf.Free;
end;
end;
Извикването на flatten е коментирано нарочно. Оставете го изключено и документът се доставя като жива форма, чиито действия се задействат в четеца. Активирайте го и полето се рендерира до статични маркировки, което е точно това, което искате, когато формата е попълнена и резултатът трябва да пътува като фиксиран запис. Едно и също поле, един и същ код, два много различни документа в зависимост от това дали го замразявате
Избор на правилния глагол
Четирите действия се разделят чисто според това какво докосват. Именуваното действие премества прозореца за преглед (viewport) и не се нуждае от поле. Действието Hide променя видимостта и се нуждае от имена на полета, като кодирането низ-спрямо-масив се обработва автоматично. Действието за импортиране на данни достига до файл на диска и следователно е забранено в PDF/A. Действието на JavaScript изпълнява произволна логика и е най-добре да бъде разделено между глобален пакет от функции и малки извиквания за конкретни действия. Посегнете към най-простото, което върши работа: действието Hide е по-преносимо от скрипт, който задава флаг за скриване, а именуваното действие е по-издръжливо от съхранена дестинация на страница, защото няма номер на страница, който да поддържате
Оттук две съседни теми завършват картината. Ако формата е част от достъпен документ, дървото на структурата, по което се движат екранните четци, е разгледано в нашата статия за маркиран PDF и структура за достъпност. Когато попълнената форма трябва да бъде заключена и подписана, работният процес е описан в ръководството за съвместимост и подписване. И трите се изграждат върху една и съща машина, която се доставя като PDF библиотека за Delphi заедно с API за създаване, форми и подписи, разгледани на други места в този блог