Artigo Técnico

Exportar TDataSet e relatórios para PDF no Delphi

Para exportar um TDataSet ou um relatório para PDF no Delphi, a losLab PDF Library oferece dois caminhos. O PDFlibTableExport adapta qualquer TDataSet — uma consulta FireDAC, um ClientDataSet, uma tabela em memória — em uma tabela PDF paginada, e três pontes sob Addons entregam um relatório FastReport, QuickReport ou ReportBuilder preparado para o mesmo gravador de PDF. Ambos produzem um PDF real sem driver de impressora e sem janela visível

Os dois problemas parecem semelhantes, mas não são. Um DBGrid ou um resultado de consulta bruto não tem layout próprio, então exportá-lo significa inventar um: colunas, cabeçalhos, quebras de página. Um documento FastReport ou ReportBuilder já carrega um layout projetado, então exportá-lo significa reproduzir fielmente os comandos de desenho de outra pessoa no espaço do PDF. A losLab PDF Library mantém essas preocupações em unidades separadas precisamente porque os modos de falha diferem, e o restante deste artigo aborda cada uma delas e onde elas deixam de ser confiáveis

Dois caminhos de exportação de PDF no Delphi alimentando tabelas de dataset e pontes de relatório em um único gerador de PDF headless
PDFlibTableExport cria uma tabela paginada para qualquer TDataSet, enquanto três bridges de Addons reproduzem layouts de FastReport, QuickReport e ReportBuilder no mesmo gravador de PDF

Como exportar um TDataSet para PDF no Delphi?

O PDFlibTableExport transforma um conjunto de dados em uma tabela em uma única chamada. O exportador percorre a lista de campos uma vez, ignora campos blob binários automaticamente — ftBlob, ftGraphic, ftBytes e seus semelhantes não têm texto de célula útil — alinha à direita colunas numéricas e pinta uma faixa zebra opcional sobre uma banda de cabeçalho estilizada. Por baixo do capô, ele constrói a grade com CreateTable, preenche as células com SetTableCellContent e renderiza com DrawTableRows, a mesma API pública de Tabela que você controlaria manualmente. O wrapper de conveniência PDFlibExportDataSet configura milímetros e uma origem superior esquerda para você, de modo que o chamador fornece apenas uma página

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');   // qualquer TDataSet funciona aqui

    PDF.SetOrigin(1);
    PDF.SetMeasurementUnits(1);              // milímetros
    PDF.SetPageSize('A4');
    PDF.AddStandardFont(4);

    Options := DefaultTableExportOptions;
    Options.Title := 'Customers';
    Options.ColumnWidth := 32;               // 11 campos cabem em A4 a 32 mm
    Options.RepeatHeader := True;            // redesenha o cabeçalho a cada página

    PDFlibExportDataSet(PDF, MemTable, 'customers.pdf', Options);
  finally
    PDF.Free;
    MemTable.Free;
  end;
end;

A omissão automática de blobs é um padrão, não uma camisa de força. Quando você precisar de controle por campo — um rótulo de coluna personalizado, uma largura menor ou a remoção de uma coluna de chave interna que não seja um blob —, construa um TPDFlibTableExporter diretamente e conecte seu evento OnFieldFilter, que é disparado uma vez por campo e fornece uma especificação mutável. Defina Include como False para descartar o campo, ou defina ColumnWidth e DisplayLabel para substituir os padrões. Esse hook também é onde você exclui uma coluna de memo larga que não deseja que infle a página

Paginação: passando o estado do desenho entre páginas

O DrawTableRows carrega o estado de paginação, e entender seu contrato é o segredo do jogo. Você o chama com uma primeira linha e uma última linha; passar uma última linha menor que 1 significa desenhar até o fim da tabela, limitado pela altura informada. A função retorna a altura que ela realmente desenhou, e GetTableLastDrawnRow informa a última linha que coube. Esse par é o estado que você passa de uma página para a outra: quando a última linha desenhada é menor que o total, você abre uma nova página e retoma a partir da linha seguinte. Nada é medido novamente — a continuação recomeça exatamente de onde a chamada anterior parou

PDF Library for Delphi: contrato de paginação do DrawTableRows mostrando linhas de tabela continuando por três páginas de PDF com uma faixa de cabeçalho repetida
DrawTableRows funciona em par com GetTableLastDrawnRow para que cada nova página retome de LastDrawn mais um e redesenhe a linha de cabeçalho
// Como o exportador continua uma tabela longa entre páginas
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
  DrawHeight := Y - Options.BottomMargin;
  // Última linha = 0 significa "desenhar até o fim", limitado por DrawHeight
  PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
  LastDrawn := PDF.GetTableLastDrawnRow(TableID);
  if LastDrawn >= TotalRows then
    Break;                       // a tabela inteira coube nesta página
  if LastDrawn < Row then
    Break;                       // segurança: nenhum progresso, aborta
  PDF.NewPage;
  Row := LastDrawn + 1;          // continua a partir da primeira linha não desenhada
  Y := PageHeight - Options.Top;
end;

O loop também é onde reside o cabeçalho repetido. Em cada página de continuação, o exportador opcionalmente desenha a linha 1 novamente — o cabeçalho — antes das linhas de dados, e usa a altura retornada por aquela primeira chamada de DrawTableRows para deslocar o corpo abaixo dela. É por isso que o DrawTableRows retorna uma altura em vez de uma coordenada de próxima linha: o valor retornado é o que permite empilhar um cabeçalho redesenhado e o corpo contínuo sem embutir o tamanho de nenhum dos dois

Como exportar um relatório FastReport ou ReportBuilder para PDF?

As pontes de relatório adotam a abordagem oposta: elas nunca inventam um layout, elas o reproduzem. Cada ponte se conecta ao seu motor no ponto de exportação nativo do motor. O PDFlibFRExport herda de TfrxCustomExportFilter e recebe os objetos de memo, imagem, forma e linha do FastReport, traduzindo cada um em um primitivo do PDF Library for Delphi. O PDFlibRBDevice herda de TppFileDevice e percorre a lista DrawCommand do ReportBuilder. O PDFlibQRExport segue por um terceiro caminho totalmente diferente, abordado na próxima seção. Todos os três funcionam em modo headless, que é o principal motivo para utilizá-los em um servidor

Headless não é de graça, e o FastReport é o exemplo de alerta. O TfrxReport.Export passa pelas páginas de visualização, que consultam a propriedade ShowDialog do filtro, e essa propriedade herda um padrão de True. Em uma máquina sem área de trabalho interativa, a caixa de diálogo modal retorna um resultado de cancelamento e a exportação falha silenciosamente. Defina ShowDialog como False antes de chamar Export e o relatório será renderizado silenciosamente. O ReportBuilder tem chaves paralelas — AllowPrintToFile e ShowPrintDialog —, e o QuickReport, que gerencia tudo por meio de Prepare, não precisa de nenhuma supressão de diálogo

var
  Exporter: TPDFlibFRExport;
begin
  Report.PrepareReport;                  // gera as páginas primeiro
  Exporter := TPDFlibFRExport.Create(nil);
  try
    Exporter.FileName := 'invoice.pdf';
    Exporter.ShowDialog := False;        // headless: pula o modal, sem cancelamento
    Report.Export(Exporter);
  finally
    Exporter.Free;
  end;
end;

Três motores, três sistemas de coordenadas

Cada ponte faz sua própria aritmética de coordenadas, porque cada motor mede o mundo de forma diferente. O PDFlibFRExport trata as posições dos objetos do FastReport como pixels a 96 PPI e escala por 96/25.4 para chegar a milímetros. O PDFlibRBDevice lê os comandos de desenho do ReportBuilder em milésimos de mm e divide por 1000, e se registra por meio de ppRegisterDevice, de modo que definir ppReport.DeviceType como 'PDF Library for Delphi' e chamar Print é suficiente para direcionar a saída através dele. O PDFlibQRExport não faz nenhuma matemática de coordenadas: uma página QuickReport preparada já é um meta-arquivo EMF, então a ponte salva cada página em um stream e o alimenta em ImportEMFFromStream, deixando o caminho de contexto de dispositivo e renderização de meta-arquivo da biblioteca posicionar cada glifo e linha. As pontes do FastReport e do ReportBuilder, por outro lado, emitem primitivos de desenho vetorial nativos e texto nativo

PDF Library for Delphi: conversões de sistema de coordenadas realizadas pelas pontes de exportação de PDF do FastReport, ReportBuilder e QuickReport
Pixels do FastReport, milésimos de milímetro do ReportBuilder e páginas EMF do QuickReport são cada um convertidos antes de chegar ao núcleo de desenho PDF
var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    PDF.SetOrigin(1);
    // Prepara o relatório e anexa todas as páginas em uma única chamada; a ponte
    // transforma o EMF de cada página em PDF por meio de ImportEMFFromStream.
    PDFlibQRExportReport(PDF, QuickRep1, 'ledger.pdf');
  finally
    PDF.Free;
  end;
end;

Onde a fidelidade termina

Conheça os limites antes de comprometer um fluxo de trabalho com essas pontes. O exportador de datasets descarta colunas binárias silenciosamente, de modo que um relatório que precisa exibir uma imagem incorporada exige uma abordagem diferente de uma tabela simples. O caminho do QuickReport, por passar por EMF, converte texto em glifos vetoriais — a página parece correta, mas não carrega texto selecionável ou pesquisável e nenhuma estrutura de PDF etiquetado (tagged PDF) para acessibilidade. O FastReport e o ReportBuilder mantêm o texto real, mas os manipuladores tipados cobrem as visualizações comuns — memo, imagem, forma, linha — e recorrem a uma caixa delimitadora ou ignoram as mais exóticas, de modo que um relatório baseado em rich text, códigos de barras ou preenchimentos de gradiente perderá detalhes. Nada disso é um defeito; é o limite real de uma camada de tradução

Uma ressalva operacional supera as demais. Nenhum dos três motores é fornecido com a losLab PDF Library, e as pontes são deliberadamente excluídas do build principal — elas compilam apenas a partir de um projeto que já tenha o FastReport VCL 6.x, o QuickReport 8 ou o ReportBuilder 20 em seu caminho de pesquisa (search path). As superfícies de meta-arquivo e DrawCommand que elas visam permaneceram estáveis em várias versões principais dos motores, mas você é o responsável pela correspondência de versões. Acerte isso e os dois caminhos cobrem toda a extensão, desde um dump DBGrid ad-hoc até uma fatura desenhada. O exportador de tabela e as pontes de relatório são fornecidos com a losLab PDF Library para Delphi e C++Builder