Pro exportování TDataSet nebo reportu do PDF v Delphi nabízí losLab PDF Library dvě cesty. PDFlibTableExport přizpůsobí jakýkoli TDataSet — dotaz FireDAC, ClientDataSet, tabulku v paměti — do paginované PDF tabulky, a tři přemostění pod Addons předají připravený report z FastReport, QuickReport nebo ReportBuilder stejnému PDF zapisovači. Oba způsoby generují skutečné PDF bez tiskového ovladače a bez viditelného okna
Tyto dva problémy vypadají podobně, ale nejsou. DBGrid nebo surový výsledek dotazu nemá své vlastní rozvržení, takže jeho export znamená nějaké navrhnout: sloupce, záhlaví, konce stránek. Dokument FastReport nebo ReportBuilder již navržené rozvržení nese, takže jeho export znamená věrně zopakovat kreslicí příkazy někoho jiného do prostoru PDF. losLab PDF Library udržuje tyto záležitosti v oddělených jednotkách právě proto, že způsoby selhání se liší, a zbytek tohoto článku se věnuje každému z nich a místům, kde přestávají být spolehlivé
Jak exportovat TDataSet do PDF v Delphi?
PDFlibTableExport převede dataset na tabulku v jediném volání. Exportér jednou projde seznam polí, automaticky přeskočí binární pole typu blob — ftBlob, ftGraphic, ftBytes a jim podobné nemají žádný užitečný text buňky — zarovná číselné sloupce doprava a vykreslí volitelné zebra pruhování přes nastylovaný pás záhlaví. Pod kapotou sestaví mřížku pomocí CreateTable, vyplní buňky pomocí SetTableCellContent a vykreslí pomocí DrawTableRows, což je stejné veřejné API tabulky, které byste ovládali ručně. Pohodlný obal PDFlibExportDataSet za vás nakonfiguruje milimetry a počátek vlevo nahoře, takže volající poskytuje pouze stránku
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'); // zde funguje libovolný TDataSet
PDF.SetOrigin(1);
PDF.SetMeasurementUnits(1); // millimetres
PDF.SetPageSize('A4');
PDF.AddStandardFont(4);
Options := DefaultTableExportOptions;
Options.Title := 'Customers';
Options.ColumnWidth := 32; // 11 polí se vejde na A4 při 32 mm
Options.RepeatHeader := True; // na každé stránce znovu vykreslete záhlaví
PDFlibExportDataSet(PDF, MemTable, 'customers.pdf', Options);
finally
PDF.Free;
MemTable.Free;
end;
end;
Automatické přeskakování blobů je výchozí nastavení, nikoli svěrací kazajka. Když potřebujete kontrolu nad jednotlivými poli — vlastní popisek sloupce, užší šířku nebo vynechání interního klíčového sloupce, který není typu blob — vytvořte přímo TPDFlibTableExporter a připojte se k jeho události OnFieldFilter, která se spustí jednou pro každé pole a předá vám modifikovatelnou specifikaci. Nastavením Include na False pole vynecháte, případně nastavte ColumnWidth a DisplayLabel pro přepsání výchozích hodnot. Tento háček (hook) je také místem, kde vyloučíte široký sloupec typu memo, který nechcete mít roztažený na celou stránku
Stránkování: předávání stavu kreslení mezi stránkami
DrawTableRows nese stav stránkování a pochopení jeho kontraktu je základem všeho. Voláte jej s prvním a posledním řádkem; předání posledního řádku menšího než 1 znamená kreslit až do konce tabulky, omezeno výškou, kterou jste zadali. Funkce vrací výšku, kterou skutečně vykreslila, a GetTableLastDrawnRow hlásí poslední řádek, který se vešel. Tato dvojice představuje stav, který předáváte z jedné stránky na druhou: pokud je poslední vykreslený řádek kratší než celkový počet, otevřete novou stránku a pokračujete od řádku následujícího. Nic se znovu neměří — pokračování navazuje přesně tam, kde předchozí volání skončilo
// Jak exportér pokračuje s dlouhou tabulkou napříč stránkami
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
DrawHeight := Y - Options.BottomMargin;
// Last row = 0 znamená „vykreslit do konce“, omezeno pomocí DrawHeight
PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
LastDrawn := PDF.GetTableLastDrawnRow(TableID);
if LastDrawn >= TotalRows then
Break; // celá tabulka se vešla na tuto stránku
if LastDrawn < Row then
Break; // safety: no forward progress, bail out
PDF.NewPage;
Row := LastDrawn + 1; // pokračujte od prvního nevykresleného řádku
Y := PageHeight - Options.Top;
end;
Smyčka je také místem, kde žije opakující se záhlaví. Na každé navazující stránce exportér volitelně vykreslí řádek 1 znovu — záhlaví — před datovými řádky a použije výšku vrácenou tímto prvním voláním DrawTableRows k posunutí těla pod ním. Proto DrawTableRows vrací výšku namísto souřadnice dalšího řádku: vrácená hodnota je to, co vám umožňuje poskládat překreslené záhlaví a pokračující tělo bez pevného kódování velikosti kteréhokoli z nich
Jak exportovat report FastReport nebo ReportBuilder do PDF?
Přemostění pro reporty volí opačný přístup: nikdy nenavrhují rozvržení, ale přehrávají ho. Každé přemostění se připojuje ke svému stroji na jeho nativním exportním rozhraní. PDFlibFRExport dědí z TfrxCustomExportFilter a přijímá objekty memo, obrázky, tvary a čáry z FastReportu a překládá každý z nich na primitivum PDF Library for Delphi. PDFlibRBDevice dědí z TppFileDevice a prochází seznam DrawCommand z ReportBuilderu. PDFlibQRExport volí zcela třetí cestu, kterou se zabývá další část. Všechny tři běží bezhlavě (headless), což je hlavním důvodem, proč po nich na serveru sáhnout
Headless režim není zadarmo a FastReport je toho varovným příkladem. TfrxReport.Export prochází přes náhledové stránky, které konzultují vlastnost filtru ShowDialog, přičemž tato vlastnost dědí výchozí hodnotu True. Na stroji bez interaktivní plochy vrátí modální dialog výsledek stornování a export potichu selže. Před voláním Export nastavte ShowDialog na False a report se vykreslí bez dialogů. ReportBuilder má paralelní přepínače — AllowPrintToFile a ShowPrintDialog — a QuickReport, který vše řídí přes Prepare, nepotřebuje potlačení dialogů vůbec
var
Exporter: TPDFlibFRExport;
begin
Report.PrepareReport; // nejdříve sestavte stránky
Exporter := TPDFlibFRExport.Create(nil);
try
Exporter.FileName := 'invoice.pdf';
Exporter.ShowDialog := False; // bezobslužný režim: přeskočte modální dialog, bez zrušení
Report.Export(Exporter);
finally
Exporter.Free;
end;
end;
Tři stroje, tři souřadnicové systémy
Každé přemostění provádí vlastní aritmetiku souřadnic, protože každý stroj měří svět jinak. PDFlibFRExport považuje pozice objektů FastReport za pixely při 96 PPI a měřítko upravuje poměrem 96/25.4, aby dosáhl milimetrů. PDFlibRBDevice čte kreslicí příkazy ReportBuilderu v tisícinách mm a dělí je 1000, přičemž se registruje přes ppRegisterDevice, takže nastavení ppReport.DeviceType na 'PDF Library for Delphi' a volání Print stačí k nasměrování výstupu přes něj. PDFlibQRExport neprovádí vůbec žádnou matematiku souřadnic: připravená stránka QuickReportu je již EMF metaoobrázek (metafile), takže přemostění uloží každou stránku do streamu a předá ji do ImportEMFFromStream, čímž nechá kontext zařízení a cestu vykreslování metasouborů knihovny umístit každý znak a pravidlo. Mosty pro FastReport a ReportBuilder naproti tomu emitují nativní vektorové kreslicí primitivy a nativní text
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
PDF.SetOrigin(1);
// Připraví report a připojí každou stránku v jednom volání; přemostění
// převede EMF každé stránky do PDF pomocí ImportEMFFromStream
PDFlibQRExportReport(PDF, QuickRep1, 'ledger.pdf');
finally
PDF.Free;
end;
end;
Kde věrnost končí
Seznamte se s hranicemi dříve, než těmto přemostěním svěříte svůj pracovní postup. Exportér datasetů potichu vynechává binární sloupce, takže report, který musí zobrazovat vložený obrázek, vyžaduje jiný přístup než přímá tabulka. Cesta přes QuickReport kvůli použití EMF převádí text na vektorové znaky — stránka vypadá správně, ale neobsahuje žádný vybratelný nebo prohledávatelný text ani strukturu tagovaného PDF pro usnadnění přístupu (accessibility). FastReport a ReportBuilder uchovávají skutečný text, ale typové obsluhy pokrývají běžné pohledy — memo, obrázek, tvar, čáru — a u těch exotických se vracejí k ohraničujícímu rámečku (bounding box) nebo je přeskakují, takže report spoléhající na bohatý text (rich text), čárové kódy nebo přechodové výplně ztratí detaily. Nic z toho není vada; je to upřímná hranice překladové vrstvy
Jedno provozní upozornění převyšuje ostatní. Žádný ze tří strojů se nedodává uvnitř losLab PDF Library a přemostění jsou záměrně vyloučena z hlavního sestavení — kompilují se pouze z projektu, který již má na své vyhledávací cestě FastReport VCL 6.x, QuickReport 8 nebo ReportBuilder 20. Rozhraní metaoobrázků (metafile) a DrawCommand, na která cílí, zůstala napříč několika hlavními verzemi strojů stabilní, ale shoda verzí je na vás. Pokud to uděláte správně, obě cesty pokryjí celou škálu, od ad-hoc výpisu DBGrid po navrženou fakturu. Exportér tabulek i přemostění reportů se dodávají s losLab PDF Library pro Delphi a C++Builder