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'); // 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;
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 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;
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 PDFlibPas. `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; // 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;
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 `'PDFlibPas'` 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