[Delphi] Exportando relatórios em QuickReport para PDF com o Synopse

[Delphi] Exportando relatórios em QuickReport para PDF com o SynopseOlá, amigos programadores!
Hoje, o objetivo é apresentar uma maneira rápida, simples e funcional de exportar relatórios elaborados em QuickReport para PDF, sem instalações de componentes de terceiros e sem a necessidade de instalar a versão Professional do componente. Quer saber como? Acompanhe!

 

Eu tenho um projeto de controle de pedidos desenvolvido em Delphi, no qual um dos recursos do software é gerar arquivos PDF dos pedidos para que os vendedores possam enviá-los por e-mail. Foi aí que a minha história com essa funcionalidade começou.

Primeiro, tentei utilizar a classe TQRPDFDocumentFilter da versão Professional do QuickReport, mas não fiquei satisfeito com a qualidade do documento gerado. Depois, tomei conhecimento do ExportPack (TExportQR), porém, não encontrei versões atualizadas deste componente para a família XE. Em seguida, tentei o componente Gnostice, mas, além de pago, o componente trouxe várias funcionalidades adicionais que eu não precisava. Ah, e sem contar que me deparei com algumas dificuldades na instalação.

Bom, continuei pesquisando, pesquisando… e encontrei o Synopse! É apenas uma biblioteca que deve ser vinculada ao projeto para gerar o PDF, sem complexidades.
Vamos fazer na prática?

O primeiro passo é baixar as bibliotecas neste link e descompactá-las em uma pasta específica de bibliotecas, como, por exemplo, “C:\BibliotecasDelphi”.
O próximo passo é apontar esse caminho no Library Path do Delphi para que as bibliotecas sejam encontradas na compilação. Para isso, no Delphi, clique no menu Tools > Options. Ao abrir a janela, navegue até Delphi Options > Library e clique no botão de reticências do Library Path. Agora, encontre o caminho da pasta de bibliotecas e clique em Add, conforme a imagem abaixo:

Incluir Synopse no Library Path do Delphi
 

O terceiro passo é adicionar a biblioteca SynPdf na seção uses do projeto. Adicione também a unit Printers, caso ainda não esteja adicionada:

uses
  SynPdf, Printers;

Pronto! Agora é só partir para a codificação:

var
  // variáveis necessárias para a geração do PDF
  oPDF: TPDFDocument;
  oMetaDados: TMetafile;
  oPosicionamento: TPDFCanvasRenderMetaFileTextPositioning;
  nPagina: integer;
begin
  // cria uma instância do documento PDF
  oPDF := TPDFDocument.Create(True, 0, False, nil);
 
  // inicializa a variável de metadados
  oMetaDados := nil;
  try
    // configura o tipo do papel
    oPDF.DefaultPaperSize := psA4;
 
    // configura o posicionamento dos caracteres
    oPosicionamento := tpKerningFromAveragePosition;
 
    // configura o layout de visualização do documento
    oPDF.Root.PageLayout := plOneColumn;
 
    // ajusta a orientação do documento, caso o relatório esteja em paisagem
    if QuickRep1.Page.Orientation = poLandscape then
    begin
      oPDF.DefaultPageLandscape := True;
    end;
 
    // prepara o relatório
    QuickRep1.Prepare;
 
    // carrega os metadados de cada página do relatório
    for nPagina := 1 to QuickRep1.QRPrinter.PageCount do
    begin
      // adiciona uma nova página no documento
      oPDF.AddPage;
 
      // carrega os metadados da página
      oMetaDados := QuickRep1.QRPrinter.PageList.GetPage(nPagina);
 
      // renderiza os metadados
      oPDF.Canvas.RenderMetaFile(oMetaDados, 1, 0, 0, oPosicionamento);
 
      // limpa a variável de metadados
      oMetaDados := nil;
    end;
 
    // salva o arquivo
    oPDF.SaveToFile('C:\Relatorios\Relatorio.pdf');
  finally
    // libera as variáveis
    oMetaDados.Free;
    oPDF.Free;
  end;
end;

Problema resolvido, pessoal! 🙂

Clique neste link e baixe um exemplo dessa funcionalidade desenvolvido em Delphi XE7, mas deixo uma observação: para que o exemplo funcionasse em qualquer computador, coloquei todos arquivos do Synopse na mesma pasta do projeto, ok?

Para tirar o máximo de proveito da biblioteca, recomendo que as outras propriedades das classes sejam exploradas, como os parâmetros de criação do “TPDFDocument”, tratamento de fontes e os tipos de posicionamento de caracteres. Mesmo assim, em caso de dúvidas, deixe um comentário!

 

Abraços e até a próxima!


 

41 comentários

    1. Olá, William, tudo bem?
      Sim, as versões mais recentes do Delphi (salvo engano, a partir da versão XE3), já disponibiliza uma plataforma para desenvolvimento de projetos mobile. Infelizmente tive pouco contato com esse recurso, mas aparentemente é funcional.

      Abraço!

  1. Boa noite, tudo beleza?

    Interessante a sua explicação, fiz uns teste e consegui gerar o relatório usando o Delphi 6 e QuickReport 3.0.9, só que nos relatórios aqui da empresa, o total de páginas também aparece no cabeçalho, isto é, página 1/5.

    No seu exemplo, como você faria para incluir o total de páginas no cabeçalho do relatório? Seria usando a instrução QuickRep1.QRPrinter.PageCount ?

    1. Olá, Airton, como vai?
      Isso mesmo, utilize as seguintes propriedades:
      QuickReport.PageNumber para acessar o número da página atual;
      QuickReport.QRPrinter.PageCount para acessar a quantidade total de páginas.

      Ou então, utilize o componente TQRSysData e configure a propriedade Data para qrsPageNumber. Ao abrir o relatório, este componente exibirá automaticamente o número da página atual.

      Abraço!

    2. Boa tarde,

      Então no momento da impressão do relatório, os comandos usados são:

      frmAlmox.QuickRep1.Prepare;
      frmAlmox.v_total_pagina:=frmAlmox.QuickRep1.QRPrinter.PageCount;
      frmAlmox.QuickRep1.Preview;

      Onde v_total_pagina é uma variável criada no form do relatório, e no evento beforeprint da band de detalhe tem a linha abaixo:

      QRL_TotPaginas.Caption:=IntToStr(QuickRep1.PageNumber) + ' / ' + IntToStr(v_total_pagina);

      É dessa forma que é criada a informação com a página corrente / total de páginas.

      Como você faria para colocar o total de páginas conforme exemplo acima?

    3. Olá, Airton.
      É justamente dessa forma mesmo. Tenho esse mesmo código no meu projeto.
      Primeiro, obtemos a quantidade de página após o Prepare e atribuímos à uma label no relatório. Depois, no evento BeforePrint, exibimos a página atual.

    4. Entendi, o detalhe é que impressão normal sem gerar o PDF, o total de páginas aparece corretamente, mas na impressão do PDF aparece zerado.
      Pelo que percebi, para o PDF a variável não esta assumindo o valor.

    5. Estranho, Airton. Deveria aparecer, já que o PDF é um “reflexo” do QuickReport.
      Vou entrar em contato por e-mail.

      Abraço!

  2. Olá André, meus parabéns pelo código. Ficou muito bom!! Principalmente porque este procedimento faz com seja impresso diretamente em PDF sem reimprimir o relatório, como acontece com os relatórios impressos em impressoras virtuais.
    Minha contribuição para o seu código é a seguinte: Caso queiram utilizar o procedimento para exportar em PDF após o relatório já estar impresso na tela, segue código abaixo. Para este exemplo abaixo, considerem o seguinte cenário: Uma unit com um TQRPREVIEW onde todos os relatórios do sistema são visualizados nesta unit, ou seja, todos os relatórios são gerados no mesmo lugar, acoplados ao QRPREVIEW. Um botão para exportar em PDF e o procedimento segue abaixo para exportar qualquer relatório já impresso na tela.
    //Código abaixo é quase 100% o código do André, apenas sem o prepare já que já está impresso e considerando as properties direto QRPREVIEW.

    procedure ExportToPDF;
    var
      // variáveis necessárias para a geração do PDF
      oPDF: TPDFDocument;
      oMetaDados: TMetafile;
      oPosicionamento: TPDFCanvasRenderMetaFileTextPositioning;
      nPagina: integer;
    begin
      // cria uma instância do documento PDF
      oPDF := TPDFDocument.Create(True, 0, False, nil);
      // inicializa a variável de metadados
      oMetaDados := nil;
      try
        // configura o tipo do papel
        oPDF.DefaultPaperSize := psA4;
        // configura o posicionamento dos caracteres
        oPosicionamento := tpKerningFromAveragePosition;
        // configura o layout de visualização do documento
        oPDF.Root.PageLayout := plOneColumn;
        // ajusta a orientação do documento, caso o relatório esteja em paisagem
        if qrprvwPadrao.QRPrinter.Orientation = poLandscape then
        begin
          oPDF.DefaultPageLandscape := True;
        end;
        // carrega os metadados de cada página do relatório
        for nPagina := 1 to qrprvwPadrao.QRPrinter.PageCount do
        begin
          // adiciona uma nova página no documento
          oPDF.AddPage;
          // carrega os metadados da página
          oMetaDados := qrprvwPadrao.QRPrinter.PageList.GetPage(nPagina);
          // renderiza os metadados
          oPDF.Canvas.RenderMetaFile(oMetaDados, 1, 0, 0, oPosicionamento);
          // limpa a variável de metadados
          oMetaDados := nil;
        end;
        // salva o arquivo
        oPDF.SaveToFile(dlgSaveDialog.FileName);
      finally
        // libera as variáveis
        oMetaDados.Free;
        oPDF.Free;
      end;
    end;

    // onde qrprvwPadrao é o TQRPREVIEW e dlgsavedialog é um TSaveDialog para pedir ao usuário o local onde salvar o PDF.

    1. Perfeito, Leonardo!
      Agradeço fortemente pela visita e pelo tempo dedicado para deixar essa contribuição!
      Também uso o TQRPreview para elaborar visualizadores personalizados e disponibilizar recursos adicionais, como opções de exportação e envio do relatório por e-mail através de uma integração com o Microsoft Outlook. Dá pra fazer bastante coisa! 🙂

      Grande abraço!

  3. Olá, boa tarde!
    Estou usando o delphi para gerar relatório, uso o TQuickRep, adicionei o QRPDFFilter para salvar em PDF, Porém estou notando que esta sumindo todos os acentos e caracteres especiais.

    1. Boa noite, Milton, tudo bem?
      Isso não deveria ocorrer. Vou entrar em contato com você, ok?
      Abraço!

  4. Bom dia ,

    Gostei muito do post , porem estou com dificuldade de agregar uma imagem ao pdf ,poderia me ajudar?

    1. Bom dia, Pedro, tudo bem?
      Não fiz o teste, mas, se existir uma imagem no relatório (QuickReport), você testou se o Synopse consegue exportá-la?

      Abraço!

  5. Opa obrigado pela resposta. Estava utilizando o QRComposite, depois que coloquei cada relatório separado funcionou certinho. Só ficou meio ruim de trocar a fonte do relatório. Mudava pelo comando SetFontAndSize, porém nada acontecia… no final tive que tirar a justificação do parágrafo para imprimir de forma correta. De qualquer forma obrigado, vou dar uma olhada em seu site para ver algumas outras postagens.

    1. Que bom que deu certo, Pedro.
      Espero que goste das outras publicações do blog também!
      Abraço!

  6. André, gostei muito de sua solução, mas não consigo fazer funcionar no Delphi 2010.
    Acho que tudo que se refere ao QRPrinter não funciona = QuickRep1.QRPrinter.PageCount retorna zero e

    oMetaDados := QuickRep1.QRPrinter.PageList.GetPage(nPagina); retorna nil.

    Pode me ajudar?

    1. Olá, Adriano!
      Vou entrar em contato para pedir mais detalhes.
      Abraço!

  7. Oi André,

    Obrigado por responder.

    Utilizo no Delphi 2010 o QuickReport 5.04.2.

    Temos 2 tipos de relatórios no Quick:

    Um para Boletos e Danfe que é uma caixa tradicional

    de banda, foi aí que eu testei o Synopse (Boleto bancário).

    Se aí eu conseguir já me dou por satisfeito, mas deu erro…

    oMetaDados := QuickRep1.QRPrinter.PageList.GetPage(nPagina); retorna nil.

    O Outro tipo é quase em toda a aplicação, de modo

    personalizado e sempre crio o relatório desta forma.

    Inclusive mudei a forma pois no Delphi 5 não usava

    o TQRPrinter.Create(RelItem);

    Assim:

    RelItem := TQuickRep.Create(nil);
    Rel := TQRPrinter.Create(RelItem);

    BeginDoc;

    ….

    EndDoc;
    Preview;

  8. Segue código para ter um botão que salva PDF em todos os relatórios, alterando o QRprev.
    Criando uma procedure genérica, mais ou menos abaixo.
    Mudança importante no typecast:

    (qrprinter.ParentReport as TQuickRep).Page.Orientation = poLandscape then

    A seguir:

    uses ...., SynPdf;
    
    procedure TQRStandardPreview.CriarPDF;
    var
      // variáveis necessárias para a geração do PDF
      oPDF: TPDFDocument;
      oMetaDados: TMetafile;
      sdialog: TSaveDialog;
      oPosicionamento: TPDFCanvasRenderMetaFileTextPositioning;
      nPagina, findx: integer;
      sext, savefile : string;
    begin
    
      try
    
        // cria uma instância do documento PDF
        oPDF := TPDFDocument.Create(True, 0, False, nil);
    
        // inicializa a variável de metadados
        oMetaDados := nil;
    
        // configura o tipo do papel
        oPDF.DefaultPaperSize := psA4;
    
        // configura o posicionamento dos caracteres
        oPosicionamento := tpKerningFromAveragePosition;
    
        // configura o layout de visualização do documento
        oPDF.Root.PageLayout := plOneColumn;
    
        // ajusta a orientação do documento, caso o relatório esteja em paisagem
        if (qrprinter.ParentReport as TQuickRep).Page.Orientation = poLandscape then
        begin
          oPDF.DefaultPageLandscape := True;
        end;
    
        // prepara o relatório
    
        // carrega os metadados de cada página do relatório
        for nPagina := 1 to QRPreview.QRPrinter.PageCount do
        begin
          // adiciona uma nova página no documento
          oPDF.AddPage;
    
          // carrega os metadados da página
          oMetaDados := QRPreview.QRPrinter.PageList.GetPage(nPagina);
    
          // renderiza os metadados
          oPDF.Canvas.RenderMetaFile(oMetaDados, 1, 0, 0, oPosicionamento);
    
          // limpa a variável de metadados
          oMetaDados := nil;
        end;
    
        sdialog := TSaveDialog.Create(Application);
        try
          sdialog.Title := SqrSaveReport;
          sdialog.Filter := '.PDF|*.PDF';
    
          sdialog.Filename := '*.PDF';
          if not sdialog.Execute then exit;
    
          sext := ExtractFileExt(sdialog.FileName);
          savefile := sdialog.FileName;
          sext := upperCase(sext);
          // enforce an extension
          if sext = '' then
          begin
            findx := sdialog.FilterIndex-1;
            if findx = 0 then
               sext := '.PDF'
            else if findx = 1 then
               sext := 'PDF';
    
            if sext[1] = '.' then sext := copy( sext, 2, 3 );
            savefile := savefile + '.' + sext;
          end;
    
          if sext[1] = '.' then sext := copy( sext, 2, 3 );
    
          if sext='PDF' then
          begin
            // salva o arquivo
            oPDF.SaveToFile(savefile);
            exit;
          end;
    
        finally
          sdialog.Free;
        end;
    
      finally
        // libera as variáveis
        oMetaDados.Free;
        oPDF.Free;
      end;
    end;
    1. Adriano, muito obrigado por ter dedicado um tempo para retornar aqui no blog e deixar a sua contribuição!
      O seu código certamente será de grande ajuda para os desenvolvedores que acessarem este artigo.

      Abração!

  9. André, bom dia. Tenho alguns sistemas e utilizo 2 sistemas em Delphi XE8 e tenho 2 sistemas em Delphi 5. Um deles esta migrando para Delphi XE8. Estou precisando de uma soçução para gerar o relatório em PDF no Delphi 5 (enquanto não migra, essa solução vai funcionar no Delphi 5. Se não, você conhece alguma solução para Delphi 5?

    1. Olá, Bruno, boa tarde!
      Vou entrar em contato com você para entender melhor este cenário de migração.
      Abraço!

  10. Tentei utilizar o código, mas quando ocorre a exportação, o arquivo salvo está todo branco, sem conteúdo nenhum. Alguém pode ajudar?
    Detalhe: estou utilizando o Delphi 7. Será que funciona?

    Atualização:
    Problema resolvido!!! Tinha esquecido de colocar os arquivos junto ao projeto. Obrigado!

    1. Olá, Eder!
      Desculpe-me por não ter respondido antes. Que bom que deu certo!
      Abraço!

  11. Oi André, tudo bem? Cara eu estou conseguindo exportar o QuickReport inclusive com imagens tudo certo… Mas, eu tenho um relatório com 200 imagens, no QuickReport ele carrega certo, porém durante a exportação utilizando o SynPDF dá “Out of memory” lá pela 160… possivelmente pela grande quantidade de imagens… Teria alguma dica para me dar? Desde já agradeço. Obrigado e Abraços!

    1. Olá, Felipe, como vai?
      Peço desculpas pela demora para respondê-lo. Retornei ontem de uma viagem.
      Felipe, o seu caso é bem curioso. Por algum motivo, a memória reservada para cada imagem aumenta para cada iteração de página.
      Vou pedir para que você faça um teste. Dentro do loop das páginas, troque esta linha:

      oMetaDados := nil;

      Por esta:

      oMetaDados.Free;

      Fico no aguardo do seu retorno.
      Abraço!

    2. Bom dia André, eu já havia tentando trocar o “oMetaDados := Nil;” por “oMetaDados.Free;” sim, mas não resolveu. Depois de muito pesquisar e testar, consegui resolver o meu problema trabalhando com TFileStream. Peguei o código no fórum da própria SynPDF. Desta forma cessou o erro de “Out of memory” e além disso, as imagens não sofreram mais perda na qualidade ao gerar o PDF. Detalhe é que eu gero PDF/A. Segue a solução:

      var
          vPagina: Integer;
          vFileStream: TFileStream;
          oPDF: TPdfDocumentGDI;
          oMetaDados: TMetafile;
          oPosicionamento: TPDFCanvasRenderMetaFileTextPositioning;
      
      begin
              oMetaDados:=Nil;
              oPosicionamento:=tpKerningFromAveragePosition;
              oPDF:=TPdfDocumentGDI.Create(True,0,True,Nil);
              oPDF.NewDoc;
      
              vFileStream:=TFileStream.Create('Arquivo.pdf',fmCreate);
              oPDF.SaveToStreamDirectBegin(vFileStream);
      
              {AQUI VOCÊ DEVE CRIAR AS PÁGINAS}
              {NO MEU CASO EU EXPORTO AS PÁGINAS DE UM QUICKREPORT COM IMAGENS}
              for vPagina := 1 to qrReport.QRPrinter.PageCount do
              begin
                     oPDF.AddPage;
                     oMetaDados:=TMetafile.Create;
                     oMetaDados:=qrReport.QRPrinter.PageList.GetPage(vPagina);
                     oPDF.Canvas.RenderMetaFile(oMetaDados,1,0,0,oPosicionamento,99,101,True);
                     FreeAndNil(oMetaDados);
                     oPDF.SaveToStreamDirectPageFlush;
              end;
              
              oPDF.SaveToStreamDirectEnd;
              vFileStream.Free;
              FreeAndNil(oPDF);
      end;

      Funcionando 100%! Espero que um dia ajude alguém =)

      Abraços!

    3. Olá, Felipe, boa noite!
      Agradeço muito pela sua colaboração! Não conhecia essa forma de exportação com o Synopse. Vale até fazer uma continuação deste artigo (claro, com todos os créditos a você!).
      Obrigado, Felipe. Essa solução certamente ajudará a comunidade Delphi!
      Abração!

      Obs: Não sabia que você era do blog “Delphi Para Iniciantes”. Já conheço e gostei bastante do conteúdo. Parabéns!

    1. Olá, Luiz Gustavo, tudo bem?
      Infelizmente não tenho experiências com ReportBuilder. Mesmo assim, fiz algumas pesquisas rápidas na web e notei que alguns desenvolvedores utilizam um componente chamado Export Devices para essa finalidade. Outros instalam impressoras virtuais que geram PDFs (como o PDF Creator). Neste caso, basta apenas chamar a função de impressão do Report Builder para que a janela para salvar o arquivo seja exibida.
      Em adição, sugiro também que você assista este vídeo do MVP Victory Fernandes:

      https://youtu.be/VGVZ27rL3eU

      Abração!

  12. Caro André,
    Primeiro gostaria de felicitar pelo seu artigo, muito bom.
    É que não consigo compilar o projeto…
    ERRO NESTA LINHA: oPDF.Canvas.RenderMetaFile(oMetaDados, 1, 0, 0, oPosicionamento);
    MENSAGEM DE ERRO: [DCC Error] minhaUnit.pas(338): E2010 Incompatible types: ‘Single’ and ‘TPdfCanvasRenderMetaFileTextPositioning’

    Tem alguma ajuda?
    Desde já agradeço!

    1. Olá, Weliton, como vai? Peço desculpas pela demora.
      Nas últimas versões do Synopse há algumas mudanças nos parâmetros de alguns métodos. O método RenderMetaFile, por exemplo, possui um parâmetro a mais, portanto, agora é necessário chamá-lo dessa forma:

      RenderMetaFile(oMetaDados, 1, 0, 0, 0, oPosicionamento);

      Abraço!

  13. Agradecido pela sua atenção. Eu já tinha descobrido por aqui, às duras penas. Mais uma vez parabenizo pelo artigo. Abraço!

    1. Certo, Weliton. Peço desculpas novamente pela demora para responder o seu comentário.

  14. Estou com um problema com as letras. Em algumas palavras o espaço entre caracteres é maior que no original, o que faz com que saia fora de retângulos.
    Sabe como resolver isto?

    1. Olá, Paulo!
      Experimente trocar o valor da variável “oPosicionamento” de tpKerningFromAveragePosition para tpExactTextCharacterPositining.
      Abraço!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *