За експортиране на TDataSet или отчет в PDF в Delphi, библиотеката losLab PDF Library предлага два начина. Методът PDFlibTableExport адаптира всеки TDataSet – FireDAC заявка, ClientDataSet, таблица в паметта – в разпределена на страници PDF таблица, а три моста (bridges) в директория Addons предават подготвен FastReport, QuickReport или ReportBuilder отчет на същия PDF модул за запис. И двата варианта генерират истински PDF файл без драйвер за принтер и без видим прозорец
Двата проблема изглеждат сходни, но не са. DBGrid или чистият резултат от заявка нямат собствена структура на оформление, така че експортирането им означава да се създаде такава на място: колони, заглавки, прекъсвания на страници. От друга страна, FastReport или ReportBuilder документ вече носи готово оформление, така че експортирането му изисква прецизно възпроизвеждане на чуждите команди за рисуване в PDF пространството. Библиотеката losLab PDF Library разделя тези процеси в отделни модули именно защото режимите на грешка са различни; остаралата част от тази статия разглежда всеки от тях и границите, до които те са надеждни
Как се експортира TDataSet в PDF в Delphi?
Методът PDFlibTableExport превръща набора от данни (dataset) в таблица с едно извикване. Експортьорът обхожда списъка с полета веднъж, автоматично пропуска двоичните blob полета – ftBlob, ftGraphic, ftBytes и техните сходни нямат полезен текст за клетките – подравнява надясно числовите колони и добавя незадължително оцветяване на редове тип „зебра“ над стилизирана заглавна лента. Под капака той изгражда решетката с CreateTable, попълва клетките с SetTableCellContent и ги рендира с DrawTableRows – същия публичен Table API, който бихте управлявали и ръчно. Помощната обвивка PDFlibExportDataSet конфигурира милиметри и горен ляв ъгъл като начална точка вместо вас, така че извикващият трябва само да предостави страница
uses
Data.DB, FireDAC.Comp.Client, FireDAC.Stan.StorageBin,
PDFlibrary, PDFlibTableExport;
var
MemTable: TFDMemTable;
PDF: TPDFlib;
Options: TPDFlibTableExportOptions;
begin
MemTable := TFDMemTable.Create(nil);
PDF := TPDFlib.Create;
try
MemTable.LoadFromFile('customer.FDS'); // any TDataSet works here
PDF.SetOrigin(1);
PDF.SetMeasurementUnits(1); // millimetres
PDF.SetPageSize('A4');
PDF.AddStandardFont(4);
Options := DefaultTableExportOptions;
Options.Title := 'Customers';
Options.ColumnWidth := 32; // 11 fields fit on A4 at 32 mm
Options.RepeatHeader := True; // redraw the header on each page
PDFlibExportDataSet(PDF, MemTable, 'customers.pdf', Options);
finally
PDF.Free;
MemTable.Free;
end;
end;
Автоматичното пропускане на blob полета е поведение по подразбиране, а не строго ограничение. Когато се нуждаете от контрол над всяко поле – персонализиран етикет на колона, по-тясна ширина или премахване на вътрешна колона с ключ, която не е blob – създайте TPDFlibTableExporter директно и се закачете за неговото събитие OnFieldFilter, което се задейства по веднъж за всяко поле и ви предава променяема спецификация. Задайте Include на False, за да премахнете полето, или настройте ColumnWidth и DisplayLabel, за да пренапишете стойностите по подразбиране. Това събитие е също и мястото, където изключвате широки memo колони, които не искате да раздуват страницата
Страниране (Pagination): предаване на състоянието на чертане между страниците
Методът DrawTableRows пренася състоянието на страниране и разбирането на неговия договор е в основата на целия процес. Вие го извиквате с първи ред и последен ред; предаването на последен ред, по-малък от 1, означава да се чертае до края на таблицата, ограничено от зададената височина. Функцията връща височината, която действително е начертала, а GetTableLastDrawnRow отчита последния ред, който се е събрал. Тази двойка е състоянието, което предавате от една страница на следващата: когато последният начертан ред е по-малък от общия брой, отваряте нова страница и продължавате от следващия ред. Нищо не се преизчислява – продължението започва точно от мястото, където е приключило предходното извикване
// How the exporter continues a long table across pages
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
DrawHeight := Y - Options.BottomMargin;
// Last row = 0 means "draw to the end", bounded by DrawHeight
PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
LastDrawn := PDF.GetTableLastDrawnRow(TableID);
if LastDrawn >= TotalRows then
Break; // the whole table fit on this page
if LastDrawn < Row then
Break; // safety: no forward progress, bail out
PDF.NewPage;
Row := LastDrawn + 1; // continue from the first undrawn row
Y := PageHeight - Options.Top;
end;
Цикълът е и мястото, където се намира повтарящата се заглавка. На всяка следваща страница експортьорът по избор чертае ред 1 отново – заглавката – преди редовете с данни и използва височината, върната от първото извикване на DrawTableRows, за да отмести тялото под нея. Ето защо DrawTableRows връща височина, а не координата на следващия ред: върнатата стойност ви позволява да подредите повторно начертаната заглавка и продължаващото тяло, без да кодирате твърдо размера на нито едно от тях
Как се експортира FastReport или ReportBuilder отчет в PDF?
Мостовете за отчети (report bridges) възприемат обратния подход: те никога не създават оформление, а само го възпроизвеждат. Всеки мост се свързва към своя двигател на неговото естествено ниво на експортиране. PDFlibFRExport насленява TfrxCustomExportFilter и получава memo, картини, фигури и линии на FastReport, преобразувайки всяко от тях в примитив на PDFlibPas. PDFlibRBDevice наследява TppFileDevice и обхожда списъка DrawCommand на ReportBuilder. PDFlibQRExport поема по напълно различен трети път, който разглеждаме в следващия раздел. И трите работят в headless режим (без потребителски интерфейс), което е основната причина да ги използвате на сървър
Headless режимът има своите особености и FastReport е показателен пример за това. Методът TfrxReport.Export преминава през страниците за предварителен преглед, които проверяват свойството ShowDialog на филтъра, а това свойство по подразбиране е True. На машина без интерактивен десктоп модалният диалогов прозорец връща отказ и експортирането тихо се проваля. Задайте ShowDialog на False преди да извикате Export и отчетът ще се рендира безшумно. ReportBuilder има аналогични превключватели – AllowPrintToFile и ShowPrintDialog – а QuickReport, който управлява всичко през Prepare, изобщо не се нуждае от заглушаване на диалогови прозорци
var
Exporter: TPDFlibFRExport;
begin
Report.PrepareReport; // build the pages first
Exporter := TPDFlibFRExport.Create(nil);
try
Exporter.FileName := 'invoice.pdf';
Exporter.ShowDialog := False; // headless: skip the modal, no cancel
Report.Export(Exporter);
finally
Exporter.Free;
end;
end;
Три двигателя, три координатни системи
Всеки мост извършва своя собствена координатна аритметика, тъй като всеки двигател измерва света по различен начин. PDFlibFRExport третира позициите на обектите във FastReport като пиксели при 96 PPI и ги мащабира с 96/25.4, за да достигне до милиметри. PDFlibRBDevice чете командите за чертане на ReportBuilder в хилядни от милиметъра и разделя на 1000, като се регистрира чрез ppRegisterDevice, така че задаването на ppReport.DeviceType на 'PDFlibPas' и извикването на Print е достатъчно, за да се насочи изходът през него. PDFlibQRExport поема по трети път: подготвената страница в QuickReport вече е EMF метафайл, така че мостът записва всяка страница в поток и я подава на ImportEMFFromStream, позволявайки на библиотеката да разположи всеки глиф и линия чрез своя път за рендиране на контекст на устройството и метафайлове. За разлика от него, мостовете FastReport и ReportBuilder излъчват нативни векторни графични примитиви и нативен текст
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
PDF.SetOrigin(1);
// Prepares the report and appends every page in one call; the bridge
// turns each page's EMF into PDF through ImportEMFFromStream.
PDFlibQRExportReport(PDF, QuickRep1, 'ledger.pdf');
finally
PDF.Free;
end;
end;
Къде приключва съвместимостта
Запознайте се с границите, преди да обвържете работния си процес с тези мостове. Експортьорът на набори от данни пропуска безшумно двоичните колони, така че отчет, който трябва да показва вградено изображение, се нуждае от различен подход от този на правата таблица. Пътят на QuickReport, тъй като преминава през EMF, записва текста като векторни глифове – страницата изглежда правилно, но не съдържа текст, който може да се маркира или търси, нито съдържа маркирана PDF структура за достъпност. FastReport и ReportBuilder запазват истинския текст, но типизираните манипулатори обхващат само общите обекти – memo, картини, форми, линии – и се връщат към ограничаваща кутия или пропускат по-сложните, така че отчет, разчитащ на форматиран текст (rich text), баркодове или градиентни запълвания, ще загуби детайли. Нищо от това не е дефект; това е реалният предел на един транслационен слой
Едно оперативно предупреждение е по-важно от останалите. Нито един от трите двигателя не се доставя в рамките на losLab PDF Library, а мостовете са умишлено изключени от основното изграждане – те се компилират само от проект, който вече има FastReport VCL 6.x, QuickReport 8, или ReportBuilder 20 в своя път за търсене. Повърхностите на метафайловете и DrawCommand, които те таргетират, останаха стабилни през няколко основни версии на двигателя, но съвпадението на версиите е ваша отговорност. Настройте това правилно и двата пътя ще покрият пълния спектър – от ad-hoc DBGrid изход до проектирана фактура. Табличният експортьор и мостовете за отчети се доставят с losLab PDF Library за Delphi и C++Builder