Technický článek

Export TDataSet a reportů do PDF v Delphi

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é

Dvě cesty exportu PDF v Delphi, jež živí tabulky datasetů a mosty sestav do jednoho headless zapisovače PDF
PDFlibTableExport vytvoří stránkovanou tabulku z jakéhokoli TDataSet, zatímco tři mosty Addons přehrají rozvržení FastReport, QuickReport a ReportBuilder do stejného PDF zapisovače

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

PDF Library for Delphi: Smlouva stránkování pro DrawTableRows ukazující řádky tabulky pokračující přes tři strany PDF s opakovaným pásem záhlaví
DrawTableRows se páruje s GetTableLastDrawnRow, takže každá nová stránka pokračuje od LastDrawn plus jedna a překreslí hlavičkový řádek
// 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

PDF Library for Delphi: Převody souřadných systémů prováděné exportními mosty do PDF FastReport, ReportBuilder a QuickReport
Pixely FastReport, tisíciny milimetru v ReportBuilder a stránky EMF v QuickReport se každé převedou dřív, než dorazí do kreslicího jádra PDF
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