Artikel Teknis

Ekspor TDataSet dan Laporan ke PDF di Delphi

Untuk mengekspor TDataSet atau laporan ke PDF di Delphi, losLab PDF Library menawarkan dua jalur. PDFlibTableExport mengadaptasi TDataSet apa pun — kueri FireDAC, ClientDataSet, tabel dalam memori — menjadi tabel PDF terpaginasi, dan tiga bridge di bawah Addons menyerahkan laporan FastReport, QuickReport, atau ReportBuilder yang telah disiapkan ke writer PDF yang sama. Keduanya menghasilkan PDF asli tanpa driver printer dan tanpa jendela yang terlihat

Kedua masalah tersebut terlihat mirip tetapi sebenarnya tidak. DBGrid atau hasil kueri mentah tidak memiliki tata letak sendiri, sehingga mengekspornya berarti harus membuat tata letak baru: kolom, header, pemisah halaman. Dokumen FastReport atau ReportBuilder sudah memiliki tata letak yang dirancang, sehingga mengekspornya berarti memutar ulang perintah penggambaran orang lain secara akurat ke dalam ruang PDF. losLab PDF Library memisahkan masalah ini dalam unit yang berbeda justru karena mode kegagalannya berbeda, dan sisa artikel ini akan membahas masing-masing opsi serta batas keandalannya

Bagaimana cara mengekspor TDataSet ke PDF di Delphi?

PDFlibTableExport mengubah dataset menjadi tabel dalam satu panggilan. Eksportir menelusuri daftar field sekali, melewatkan field blob biner secara otomatis — ftBlob, ftGraphic, ftBytes dan sejenisnya tidak memiliki teks sel yang berguna — menyelaraskan kolom numerik ke kanan, dan menggambar garis zebra opsional di atas pita header yang bergaya. Di balik layar, ia membangun kisi dengan CreateTable, mengisi sel dengan SetTableCellContent, dan merender dengan DrawTableRows, API Tabel publik yang sama yang akan Anda kendalikan secara manual. Wrapper kenyamanan PDFlibExportDataSet mengonfigurasi milimeter dan asal kiri-atas untuk Anda, sehingga pemanggil hanya perlu menyediakan halaman

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;

Pelewatan blob otomatis adalah bawaan, bukan batasan mutlak. Saat Anda memerlukan kontrol per-field — label kolom kustom, lebar yang lebih sempit, atau membuang kolom kunci internal yang bukan blob — buat TPDFlibTableExporter secara langsung dan kaitkan ke event OnFieldFilter-nya, yang dipicu sekali per field dan memberi Anda spesifikasi yang dapat diubah. Atur Include to False untuk membuang field, atau atur ColumnWidth dan DisplayLabel untuk menimpa nilai bawaan. Kait tersebut juga merupakan tempat Anda mengecualikan kolom memo lebar yang tidak ingin Anda buat memperbesar halaman

Paginasi: meneruskan status penggambaran antar halaman

DrawTableRows membawa status paginasi, dan memahami kontraknya adalah inti dari seluruh proses. Anda memanggilnya dengan baris pertama dan baris terakhir; meneruskan baris terakhir di bawah 1 berarti menggambar hingga akhir tabel, yang dibatasi oleh tinggi yang Anda berikan. Fungsi ini mengembalikan tinggi yang sebenarnya digambar, dan GetTableLastDrawnRow melaporkan baris terakhir yang muat. Pasangan tersebut adalah status yang Anda serahkan dari satu halaman ke halaman berikutnya: ketika baris terakhir yang digambar kurang dari total, Anda membuka halaman baru dan melanjutkan dari baris setelahnya. Tidak ada yang diukur ulang — kelanjutan dimulai tepat di mana panggilan sebelumnya berhenti

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

Perulangan ini juga merupakan tempat header berulang berada. Pada setiap halaman kelanjutan, eksportir secara opsional menggambar baris 1 lagi — header — sebelum baris data, dan menggunakan tinggi yang dikembalikan oleh panggilan DrawTableRows pertama untuk mengimbangi body di bawahnya. Inilah sebabnya mengapa DrawTableRows mengembalikan tinggi daripada koordinat baris berikutnya: nilai pengembalian adalah hal yang memungkinkan Anda menumpuk header yang digambar ulang dan body kelanjutan tanpa perlu melakukan hard-coding pada ukuran salah satunya

Bagaimana cara mengekspor laporan FastReport atau ReportBuilder ke PDF?

Bridge laporan mengambil pendekatan sebaliknya: mereka tidak pernah membuat tata letak baru, melainkan memutar ulang tata letak yang sudah ada. Setiap bridge terhubung ke engine-nya pada bagian ekspor asli engine tersebut. PDFlibFRExport membuat subclass dari TfrxCustomExportFilter dan menerima objek memo, gambar, bentuk, dan garis milik FastReport, lalu menerjemahkan masing-masing ke dalam primitif PDFlibPas. PDFlibRBDevice membuat subclass dari TppFileDevice dan menelusuri daftar DrawCommand milik ReportBuilder. PDFlibQRExport mengambil rute ketiga yang sama sekali berbeda, yang akan dibahas pada bagian berikutnya. Ketiganya berjalan secara headless, yang merupakan alasan utama untuk menggunakannya di server

Mode headless tidaklah gratis, dan FastReport adalah contoh peringatannya. TfrxReport.Export diarahkan melalui halaman pratinjau, yang merujuk pada properti ShowDialog milik filter, dan properti tersebut mewarisi nilai bawaan True. Pada mesin tanpa desktop interaktif, dialog modal mengembalikan hasil batal dan ekspor gagal secara diam-diam. Atur ShowDialog ke False sebelum Anda memanggil Export dan laporan akan merender secara senyap. ReportBuilder memiliki switch paralel — AllowPrintToFile dan ShowPrintDialog — dan QuickReport, yang menggerakkan segalanya melalui Prepare, tidak memerlukan penekanan dialog sama sekali

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;

Tiga engine, tiga sistem koordinat

Setiap bridge melakukan aritmatika koordinatnya sendiri, karena setiap engine mengukur dunia secara berbeda. PDFlibFRExport memperlakukan posisi objek FastReport sebagai piksel pada 96 PPI dan menskalakannya dengan 96/25.4 untuk mencapai milimeter. PDFlibRBDevice membaca perintah gambar ReportBuilder dalam seperseribu mm dan membaginya dengan 1000, serta mendaftarkan dirinya melalui ppRegisterDevice, sehingga mengatur ppReport.DeviceType ke 'PDFlibPas' dan memanggil Print sudah cukup untuk mengarahkan output melaluinya. PDFlibQRExport mengambil rute ketiga yang sama sekali berbeda, yang dibahas pada bagian berikutnya. Halaman QuickReport yang disiapkan sudah berupa metafile EMF, sehingga bridge menyimpan setiap halaman ke stream dan memasukkannya ke ImportEMFFromStream, membiarkan jalur rendering konteks-perangkat dan metafile milik pustaka menempatkan setiap glyph dan garis. Sebaliknya, bridge FastReport dan ReportBuilder memancarkan primitif gambar vektor asli dan teks asli

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;

Di mana presisi berakhir

Eksportir dataset membuang kolom biner secara diam-diam, sehingga laporan yang harus menampilkan gambar tersemat memerlukan pendekatan yang berbeda dari tabel biasa. Jalur QuickReport, karena melalui EMF, mengubah teks menjadi glyph vektor — halaman terlihat benar tetapi tidak berisi teks yang dapat dipilih atau dicari dan tidak ada struktur PDF bertag untuk aksesibilitas. FastReport dan ReportBuilder mempertahankan teks asli, tetapi handler bertipe mencakup tampilan umum — memo, gambar, bentuk, garis — dan beralih ke bounding box atau melewatkan yang eksotis, sehingga laporan yang mengandalkan teks kaya, barcode, atau pengisian gradien akan kehilangan detail. Semua ini bukanlah cacat; ini adalah batasan jujur dari lapisan penerjemahan

Satu peringatan operasional lebih penting daripada yang lain. Tidak satu pun dari ketiga engine tersebut yang disertakan di dalam losLab PDF Library, dan bridge tersebut sengaja dikecualikan dari build utama — mereka hanya dapat dikompilasi dari proyek yang sudah memiliki FastReport VCL 6.x, QuickReport 8, atau ReportBuilder 20 pada jalur pencariannya. Permukaan metafile dan DrawCommand yang mereka targetkan tetap stabil di beberapa versi engine utama, tetapi Anda yang bertanggung jawab atas kecocokan versi. Lakukan dengan benar dan kedua jalur tersebut mencakup seluruh rentang, mulai dari dump DBGrid ad-hoc hingga faktur yang dirancang. Eksportir tabel dan bridge laporan keduanya disertakan dengan losLab PDF Library untuk Delphi dan C++Builder