Technikai cikk

TDataSet és jelentések exportálása PDF-be Delphiben

Egy TDataSet vagy egy jelentés PDF-be történő exportálásához Delphiben a losLab PDF Library két utat kínál. A PDFlibTableExport bármely TDataSet-et – legyen az FireDAC lekérdezés, ClientDataSet vagy egy memóriabeli táblázat – oldalszámozott PDF-táblázattá alakít, az Addons alatt található három híd pedig átadja az előkészített FastReport, QuickReport vagy ReportBuilder jelentést ugyanannak a PDF-írónak. Mindkettő valódi PDF-et állít elő nyomtató-illesztőprogram és látható ablak nélkül

A két probléma hasonlónak tűnik, de nem az. Egy DBGrid-nek vagy egy nyers lekérdezési eredménynek nincs saját elrendezése, így az exportálása egy elrendezés kitalálását jelenti: oszlopok, fejlécek, oldaltörések. Egy FastReport vagy ReportBuilder dokumentum már eleve tartalmaz egy tervezett elrendezést, így az exportálása azt jelenti, hogy hűen újra kell játszani valaki más rajzolási parancsait a PDF térben. A losLab PDF Library pontosan azért tartja ezeket a szempontokat külön unitokban, mert a hibaforrások eltérnek, és a cikk hátralévő része végigveszi mindegyiket, valamint azt, hogy hol válnak már megbízhatatlanná

Hogyan exportálható a TDataSet PDF-be Delphiben?

A PDFlibTableExport egyetlen hívással táblázattá alakítja az adatkészletet. Az exportőr egyszer végigjárja a mezőlistát, automatikusan kihagyja a bináris blob mezőket – az ftBlob, ftGraphic, ftBytes és rokonaik nem tartalmaznak hasznos cellaszöveget –, jobbra igazítja a numerikus oszlopokat, és egy opcionális zebracsíkozást fest a formázott fejlécre. A háttérben a rácsot a CreateTable segítségével építi fel, a cellákat a SetTableCellContent függvénnyel tölti fel, és a DrawTableRows segítségével rendereli, ami ugyanaz a nyilvános Table API, amelyet manuálisan is vezérelne. A PDFlibExportDataSet kényelmi wrapper konfigurálja a millimétereket és a bal felső kezdőpontot Ön helyett, így a hívónak csak egy oldalt kell megadnia

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;

Az automatikus blob-kihagyás alapértelmezett, nem pedig kényszerzubbony. Ha mezőnkénti vezérlésre van szüksége – egyéni oszlopcímkére, szűkebb szélességre, vagy egy olyan belső kulcsoszlop elhagyására, amely nem blob –, hozzon létre közvetlenül egy TPDFlibTableExporter objektumot, és iratkozzon fel az OnFieldFilter eseményére, amely mezőnként egyszer fut le, és egy módosítható specifikációt ad át Önnek. Állítsa az Include értéket False-ra a mező elhagyásához, vagy állítsa be a ColumnWidth és DisplayLabel értékeket az alapértelmezések felülírásához. Ez a hook az a hely is, ahol kizárhat egy széles memo oszlopot, amelyet nem szeretne, hogy túlméretezze az oldalt

Oldalszámozás: a rajzolási állapot átadása az oldalak között

A DrawTableRows hordozza az oldalszámozási állapotot, és ennek a működésnek a megértése a kulcs. Egy első és egy utolsó sorral hívja meg; ha az utolsó sor értéke 1-nél kisebb, az azt jelenti, hogy a táblázat végéig kell rajzolni, a megadott magassággal korlátozva. A függvény a ténylegesen kirajzolt magasságot adja vissza, a GetTableLastDrawnRow pedig a legutolsó befért sort jelzi. Ez a pár alkotja azt az állapotot, amelyet egyik oldalról a másikra ad át: ha a legutoljára rajzolt sor kevesebb, mint az összes, új oldalt nyit, és az azt követő sorral folytatja. Semmi sem lesz újra mérve – a folytatás pontosan ott folytatódik, ahol az előző hívás abbamaradt

// 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;

A ciklus az a hely is, ahol az ismétlődő fejléc él. Minden folytatólagos oldalon az exportőr opcionálisan újra kirajzolja az 1. sort – a fejlécet – az adatsorok előtt, és az első DrawTableRows hívás által visszaadott magasságot használja a törzs eltolásához. Ezért ad vissza a DrawTableRows magasságot a következő sor koordinátája helyett: a visszatérési érték az, ami lehetővé teszi az újrarajzolt fejléc és a folytatódó törzs egymásra helyezését anélkül, hogy bármelyik méretét fixen kódolná

Hogyan exportálható a FastReport vagy ReportBuilder jelentés PDF-be?

A jelentéshidak az ellenkező megközelítést alkalmazzák: soha nem találnak ki elrendezést, hanem újra lejátszanak egyet. Mindegyik híd a motor saját natív exportálási csatlakozójához kapcsolódik. A PDFlibFRExport a TfrxCustomExportFilter alosztálya, és fogadja a FastReport memo, picture, shape és line objektumait, mindegyiket lefordítva egy PDFlibPas primitívre. A PDFlibRBDevice a TppFileDevice alosztálya, and bejárja a ReportBuilder DrawCommand listáját. A PDFlibQRExport egy teljesen harmadik utat választ, amelyet a következő szakasz tárgyal. Mindhárom headless módban fut, ami az egyetlen ok arra, hogy szerveren használjuk őket

A headless működés nincs ingyen, és a FastReport az intő példa. A TfrxReport.Export a nézőképes oldalakon keresztül fut, amelyek lekérik a szűrő ShowDialog tulajdonságát, és ez a tulajdonság alapértelmezetten True értéket örököl. Interaktív asztal nélküli gépen a modális párbeszédpanel elutasítási (cancel) eredményt ad vissza, és az exportálás csendben meghiúsul. Állítsa a ShowDialog értékét False-ra az Export hívása előtt, és a jelentés csendben renderelődik. A ReportBuilder párhuzamos kapcsolókkal rendelkezik – AllowPrintToFile és ShowPrintDialog –, a QuickReport pedig, amely mindent a Prepare-en keresztül vezérel, egyáltalán nem igényel párbeszédpanel-letiltást

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;

Három motor, three koordináta-rendszer

Mindegyik híd elvégzi a saját koordináta-aritmetikáját, mivel mindegyik motor másképp méri a világot. A PDFlibFRExport a FastReport objektumpozíciókat pixelként kezeli 96 PPI mellett, és 96/25,4 arányban skálázza a milliméterek eléréséhez. A PDFlibRBDevice a ReportBuilder rajzolási parancsait milliméter-ezredben olvassa be és osztja 1000-rel, valamint regisztrálja magát a ppRegisterDevice segítségével, így a ppReport.DeviceType beállítása 'PDFlibPas'-ra és a Print meghívása elegendő ahhoz, hogy a kimenetet rajta keresztül irányítsa. A PDFlibQRExport egyáltalán nem végez koordináta-matematikát: egy előkészített QuickReport oldal már eleve egy EMF metafile, így a híd minden oldalt egy streambe ment, és átadja az ImportEMFFromStream függvénynek, lehetővé téve a könyvtár eszköz-kontextus és metafile renderelési útvonalának minden glif és vonal elhelyezését. Ezzel szemben a FastReport és ReportBuilder hidak natív vektoros rajzolási primitíveket és natív szöveget bocsátanak ki

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;

Ahol a pontosság véget ér

Ismerje meg a határokat, mielőtt munkafolyamatot bízna ezekre a hidakra. Az adatkészlet-exportőr csendben elhagyja a bináris oszlopokat, így egy beágyazott képet megjelenítő jelentésnek más megközelítésre van szüksége, mint egy egyszerű táblázatnak. A QuickReport útvonal, mivel EMF-en megy keresztül, a szöveget vektoros glifekké alakítja – a lap jól néz ki, de nem tartalmaz kijelölhető vagy kereshető szöveget, sem címkézett PDF struktúrát a hozzáférhetőséghez. FastReport és ReportBuilder megtartja a valódi szöveget, de a típusos kezelők lefedik a gyakori nézeteket – memo, picture, shape, line –, és egy határolókeretre esnek vissza, vagy kihagyják a különlegeseket, így a formázott szövegre, vonalkódokra vagy színátmenetes kitöltésekre támaszkodó jelentés elveszíti a részleteket. Ebből semmi sem hiba; ez egy fordítási réteg őszinte határa

Egy működési figyelmeztetés megelőzi a többit. A három motor egyike sem érkezik a losLab PDF Library-vel együtt, és a hidak szándékosan ki vannak zárva a fő buildből – csak olyan projektből fordulnak le, amelynek keresési útvonalán már szerepel a FastReport VCL 6.x, QuickReport 8 vagy ReportBuilder 20. Az általuk megcélzott metafile és DrawCommand felületek stabilak maradtak több nagyobb motorverzión keresztül, de a verzióegyezés az Ön felelőssége. Ha ezt eltalálja, a két út lefedi a teljes skálát az eseti DBGrid kimentéstől a tervezett számláig. A táblázatexportőr és a jelentéshidak egyaránt megtalálok a Delphihez és C++Builderhez készült losLab PDF Library-ben