Artigo Técnico

Imprimir um PDF Carregado no Delphi com o HotPDF e TPrinter

O HotPDF imprime um PDF carregado numa impressora física, em qualquer contexto de dispositivo Windows ou na área de transferência através de três chamadas: o PrintLoadedDocument controla um TPrinter de VCL, o RenderLoadedPageToDC pinta uma página diretamente em qualquer HDC, e o RenderLoadedPageToClipboard copia uma página como um bitmap ou um meta-ficheiro vetorial. O HotPDF é um componente PDF nativo de VCL para Delphi e C++Builder, e adicionou este caminho de impressão de primeira classe na v2.331.0, refinando-o na v2.348.0, para que já não precise de programar manualmente um loop de TPrinter em torno de um renderizador de bitmap. Todas as três chamadas desenham o conteúdo vetorial da página diretamente onde é necessário, para que a impressão permaneça tão nítida quanto a impressora consiga renderizar

Por que desenhar uma página PDF diretamente na impressora em vez de usar um bitmap?

Uma página PDF é um programa de operadores de desenho vetorial — caminhos, texto e imagens executados contra o modelo gráfico ISO 32000-1 —, e não uma grelha de píxeis. A forma óbvia de imprimir uma é rasterizar a página para um bitmap e transferir (blit) esse bitmap para a tela (canvas) da impressora; funciona, mas desperdiça a única coisa em que uma impressora é realmente boa. Uma impressora laser moderna resolve 600 ou 1200 DPI; um bitmap rasterizado a 150 ou 300 DPI e depois esticado para caber chega desfocado, com arestas de texto serrilhadas e interpolação visível. O RenderLoadedPageToDC segue o melhor caminho: reproduz o fluxo de conteúdo da página como chamadas GDI emites diretamente no contexto de dispositivo de destino, para que o texto vetorial e os desenhos de linha cheguem à impressora como vetores e sejam rasterizados apenas uma vez, pela impressora, na própria resolução da impressora

Como imprimir um PDF carregado numa impressora física no Delphi?

O HotPDF imprime um documento carregado com o PrintLoadedDocument(Printer, Options), que recebe um TPrinter de VCL atribuído e um registo THPDFPrintOptions, imprime as páginas selecionadas e devolve a contagem de páginas enviadas. Configure primeiro o Printer.PrinterIndex para escolher um dispositivo, preencha um registo de opções — a função de classe THPDFPrintOptions.Default fornece-lhe valores razoáveis — e chame-o uma vez. A biblioteca abre o trabalho de impressão com o Printer.BeginDoc, percorre o intervalo de páginas emitindo um Printer.NewPage entre páginas, e encerra-o com o Printer.EndDoc, para que nunca precise de gerir o ciclo de vida do trabalho de impressão manualmente

uses Printers;

var
  Pdf: THotPDF;
  Opts: THPDFPrintOptions;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoice.pdf') > 0 then
    begin
      Printer.PrinterIndex := -1;         // -1 seleciona a impressora padrão
      Opts := THPDFPrintOptions.Default;  // todas as páginas, 300 DPI, FitPage, cor
      Opts.PageRange := '1,3-5';          // baseado em 1; vazio imprime todas as páginas
      Opts.Copies := 2;
      Pdf.PrintLoadedDocument(Printer, Opts);
    end;
  finally
    Pdf.Free;
  end;
end;

A string PageRange é baseada em 1 e é lida da forma como uma caixa de diálogo de impressão faz: '1,3-5' imprime a página 1 e as páginas 3 a 5, e uma string vazia imprime todo o documento. O Copies mapeia diretamente para o Printer.Copies. Seja honesto consigo mesmo sobre o Collate, no entanto: porque a VCL expõe a intercalação (collation) de forma inconsistente entre as versões do Delphi, o HotPDF define a contagem de cópias e deixa a intercalação para o padrão do controlador (driver) da impressora; por isso, quando necessita de uma ordem de intercalação garantida, configure-a no controlador através do TPrinterSetupDialog em vez de confiar que a flag a transmita

Como o DPI e o FitPage decidem a escala de impressão?

O FitPage e o DPI interagem de uma forma que vale a pena compreender antes de lançar a aplicação. Com o FitPage definido como True — o padrão —, o HotPDF lê a MediaBox de cada página, redimensiona-a para Printer.PageWidth e Printer.PageHeight preservando a proporção (aspect ratio), e renderiza na escala que faz com que a página preencha o papel. Nesse modo, o Options.DPI é efetivamente substituído pela escala de ajuste, porque a saída tem de corresponder às dimensões físicas da página da impressora. Com o FitPage definido como False, o HotPDF renderiza cada página 1:1 exatamente em Options.DPI, mapeando 72 unidades de PDF para píxeis do dispositivo em DPI, que é o que deseja quando a página de origem e o papel já têm o mesmo tamanho e se importa com uma resolução exata. Recorra ao FitPage quando o tamanho da página de origem e o tamanho da gaveta (tray) de papel diferirem; recorra a 1:1 quando coincidirem e desejar uma escala determinística

Duas opções adicionadas na v2.348.0 corrigem as impressões que parecem quase corretas. Cada impressora física possui uma margem física — um bordo não imprimível que o hardware não consegue alcançar — e, por padrão, a origem da página fica dentro dela, pelo que uma página em sangria total perde uma faixa em dois bordos. Defina IgnoreHardMargin como True e o HotPDF consulta as propriedades PHYSICALOFFSETX e PHYSICALOFFSETY da impressora, deslocando a origem do dispositivo para cancelar esse desvio, fazendo com que a origem da página seja mapeada para o canto real do papel. A segunda opção, AutoRotate, está ativa por padrão: lê a MediaBox de cada página e, quando uma página é mais larga do que alta, muda a impressora para o modo paisagem (landscape) para que uma página larga seja impressa sem cortes, em vez de ser recortada em modo retrato (portrait). O AutoRotate afasta-se no momento em que define a orientação explicitamente, pelo que uma escolha deliberada de orientação vence sempre

Como renderizar uma página PDF em qualquer contexto de dispositivo?

O RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) é a primitiva por baixo do loop de impressão, e é útil por si só. O PrintLoadedDocument chama-o uma vez por página contra a tela (canvas) da impressora, mas o destino pode ser qualquer HDC do Windows: um DC de memória fora do ecrã, a canvas de um controlo num visualizador personalizado ou uma superfície de pré-visualização de impressão. O PageIndex é baseado em zero, W e H são as unidades de dispositivo da superfície de destino, e o renderizador percorre o /Contents da página e emite chamadas GDI no seu DC. Vale a pena conhecer um comportamento deliberado: não desenha preenchimento de fundo. O chamador é o proprietário da superfície, pelo que se desejar papel branco por trás da página, preenche o DC por si mesmo antes de renderizar, que é exatamente o que lhe permite compor uma página sobre uma canvas existente quando desejar

// Desenha a página 1 numa superfície de 24 bits fora do ecrã a 150 DPI
var
  Surface: TBitmap;
begin
  Surface := TBitmap.Create;
  try
    Surface.PixelFormat := pf24bit;
    Surface.SetSize(1275, 1650);           // 8.5 x 11 polegadas a 150 DPI
    Surface.Canvas.Brush.Color := clWhite;
    Surface.Canvas.FillRect(Surface.Canvas.ClipRect);  // o chamador pinta o fundo
    Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
    // A superfície contém agora a página composta, pronta para blit ou gravação
  finally
    Surface.Free;
  end;
end;

Quando apenas necessita de um TBitmap concluído — uma miniatura (thumbnail), uma exportação de imagem, uma pré-visualização para cache —, recorra ao RenderLoadedPageToBitmap, abordado no artigo complementar sobre renderizar páginas PDF para um bitmap. A diferença está em quem detém a propriedade da superfície: a chamada do bitmap aloca e devolve um buffer de píxeis, enquanto o RenderLoadedPageToDC desenha num contexto de dispositivo que já possui, que é exatamente o que a canvas da impressora ou um manipulador de pintura lhe entregam. Ambos partilham o mesmo interpretador de fluxo de conteúdo e motor de glifos incorporados, pelo que o texto sai com os contornos exatos incorporados de qualquer das formas

Como colar uma página PDF no Word ou num editor de imagens?

O RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile) coloca uma página na área de transferência do Windows, pronta a ser colada no Word, PowerPoint, num e-mail ou num editor de imagem. A flag AsMetafile escolhe o formato, e a escolha importa mais do que parece. Passe False e o HotPDF coloca um bitmap de dispositivo de 24 bits (CF_BITMAP) renderizado na sua DPI — uma grelha fixa de píxeis que pode ser colada em qualquer lugar mas que se pixeliza quando o recetor a redimensiona. Passe True e coloca um meta-ficheiro melhorado (enhanced metafile) (CF_ENHMETAFILE) no qual o texto da página e a arte vetorial viajam como comandos de desenho GDI, pelo que o resultado colado permanece nítido em qualquer nível de zoom e ajusta-se a qualquer tamanho que o documento de destino lhe dê

// Copia a página 2 como um meta-ficheiro vetorial escalável para colar no Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);

// Copia a mesma página como um bitmap de 300 DPI fixo
Pdf.RenderLoadedPageToClipboard(1, 300, False);

Onde o caminho de impressão termina

Tudo nesta página é exclusivo do Windows, e vale a pena dizê-lo claramente. O PrintLoadedDocument, o RenderLoadedPageToDC e o RenderLoadedPageToClipboard são baseados em TPrinter, no modelo de contexto de dispositivo GDI, na área de transferência do Windows e em meta-ficheiros melhorados — todas estruturas de Win32 e Win64. Noutros destinos, o resto do HotPDF continua a criar, editar e ler PDF, mas esta superfície de área de transferência e impressão apoiada em GDI não está disponível. O renderizador também visa o subconjunto comum de documentos de texto, caminhos e imagens, pelo que provas de design com muitos gradientes e impressões de cores geridas por ICC são impressas como uma pré-visualização fiel e não como uma prova colorimétrica, o mesmo limite que o renderizador de bitmap possui

Para a impressão em lote de muitos ficheiros, carregue e imprima cada documento sucessivamente e preste atenção ao consumo de memória: o PrintLoadedDocument renderiza uma página de cada vez, mas o próprio gráfico do documento carregado permanece na memória durante todo o trabalho. Quando controla uma fila de impressão com centenas de ficheiros grandes, a Direct File API de fluxo para fluxos de trabalho de PDFs grandes combina bem com o caminho de impressão — divida, funda ou selecione primeiro as páginas que necessita no disco e, em seguida, carregue apenas o resultado reduzido para imprimir. É desta forma que o PrintLoadedDocument, o RenderLoadedPageToDC e o RenderLoadedPageToClipboard são fornecidos como parte do HotPDF Component para Delphi e C++Builder — uma biblioteca VCL nativa sem dependências de DLLs externas que abrange a criação, edição, extração de texto, renderização e agora impressão de PDF diretamente para TPrinter, qualquer contexto de dispositivo ou para a área de transferência