As coordenadas do PDF estão em pontos, as coordenadas da impressora estão em unidades de dispositivo, e as duas não têm nada a ver uma com a outra até que você as converta deliberadamente. Essa incompatibilidade é a raiz da maioria das saídas de impressão incorretas em aplicativos Delphi: o código envia o arquivo certo, mas a página sai cortada, esticada ou em branco. O Componente PDFium lida com o lado da renderização de forma limpa; a infraestrutura da impressora é VCL padrão. Os dois se encaixam com uma quantidade modesta de código, uma vez que você entenda o que cada lado espera
Como o pipeline renderizar-então-imprimir funciona
O Componente PDFium não se comunica diretamente com as impressoras. O padrão é: renderizar uma página em um TBitmap na resolução que você deseja, depois transferir esse bitmap para o canvas da impressora usando StretchDIBits. O TPdf.RenderPage retorna um bitmap de propriedade do chamador, de modo que você controla as dimensões em pixels. Passe [rePrinting] no conjunto de opções e o PDFium mudará seu caminho de renderização para um que omite efeitos apenas de tela, como hinting de subpixel LCD, e lida corretamente com a MediaBox da página para a saída de impressão. Deixe rePrinting de fora e o que você enviará para a impressora será uma renderização de tela, que parece ótima em um monitor, mas tende a produzir uma saída mais suave em impressoras de alto DPI porque as decisões de hinting tomadas para telas de 96 DPI não são adequadas para impressão a 300 ou 600 DPI
O TPdf.Active é o único portão a verificar antes de tocar em qualquer propriedade da página. O componente engole erros de carregamento silenciosamente: definir Active := True em um arquivo danificado ou protegido por senha não levanta uma exceção; simplesmente deixa o Active como False. Sempre verifique isso após a atribuição. Ler PageCount ou PageWidth em um documento inativo retorna zero, o que produz operações silenciosas sem efeito (no-ops) que são muito difíceis de diagnosticar assim que chegam ao spooler
Um loop de impressão mínimo
O caso prático mais simples carrega um arquivo, abre um trabalho de impressão, itera pelas páginas e fecha. O único detalhe complicado é que Printer.NewPage não deve ser chamado antes da primeira página, daí o sinalizador FirstPage. A transferência via StretchDIBits passa por GetDIBSizes e GetDIB para extrair bits independentes de dispositivo do manipulador do bitmap e, em seguida, os pinta no canvas da impressora no tamanho total da página:
procedure PrintPdfFile(const FileName: string);
var
Pdf: TPdf;
I: Integer;
Bitmap: TBitmap;
InfoHeaderSize, ImageSize: DWORD;
InfoHeader: PBitmapInfo;
Image: Pointer;
FirstPage: Boolean;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
Exit; // load failed silently; bail out
Printer.Title := Pdf.Title;
Printer.BeginDoc;
try
FirstPage := True;
for I := 1 to Pdf.PageCount do
begin
if FirstPage then
FirstPage := False
else
Printer.NewPage;
Pdf.PageNumber := I;
// Render at printer resolution; rePrinting adjusts the render path
Bitmap := Pdf.RenderPage(
0, 0,
Printer.PageWidth,
Printer.PageHeight,
ro0,
[rePrinting]
);
try
GetDIBSizes(Bitmap.Handle, InfoHeaderSize, ImageSize);
InfoHeader := AllocMem(InfoHeaderSize);
try
Image := AllocMem(ImageSize);
try
GetDIB(Bitmap.Handle, 0, InfoHeader^, Image^);
StretchDIBits(
Printer.Canvas.Handle,
0, 0, Printer.PageWidth, Printer.PageHeight,
0, 0, Bitmap.Width, Bitmap.Height,
Image, InfoHeader^, DIB_RGB_COLORS, SRCCOPY
);
finally
FreeMem(Image);
end;
finally
FreeMem(InfoHeader);
end;
finally
Bitmap.Free;
end;
end;
finally
Printer.EndDoc;
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Passar Printer.PageWidth e Printer.PageHeight como as dimensões do bitmap significa que você renderiza no tamanho nativo de pixels da impressora, o que já considera o DPI do dispositivo. A chamada a StretchDIBits então mapeia esses pixels em 1:1 na página. Isso lhe dá a melhor fidelidade possível sem qualquer aritmética explícita de DPI, mas funciona apenas quando a página do PDF e o papel físico coincidentemente tiverem o mesmo tamanho. Quando eles divergem, você precisa de dimensionamento explícito
Dimensionamento quando o tamanho da página e do papel divergem
Uma página PDF no formato A4 retrato não se ajusta automaticamente a uma impressora configurada para Carta (US Letter), e uma página paisagem enviada a uma impressora orientada como retrato será cortada. A abordagem padrão é calcular um fator de escala uniforme a partir da proporção de pixels da impressora para pontos do PDF e, em seguida, aplicá-lo a ambas as dimensões para que a proporção da imagem (aspect ratio) seja preservada. O Pdf.PageWidth e Pdf.PageHeight expõem as dimensões atuais da página em pontos, onde um ponto equivale a 1/72 polegada. Multiplicar por um DPI de destino e dividir por 72 converte para pixels nessa resolução. Use Min nas proporções X e Y para obter a maior escala que ainda caiba dentro da área de impressão:
// Fit PDF page to printable area, preserving aspect ratio
var
ScaleX, ScaleY, Scale: Double;
DestWidth, DestHeight: Integer;
Dpi: Integer;
begin
Dpi := 300; // target render resolution
Pdf.PageNumber := PageIndex;
ScaleX := Printer.PageWidth / (Pdf.PageWidth * Dpi / 72);
ScaleY := Printer.PageHeight / (Pdf.PageHeight * Dpi / 72);
Scale := Min(ScaleX, ScaleY);
// Clamp to 1.0 for shrink-to-fit only (no enlargement)
if Scale > 1.0 then Scale := 1.0;
DestWidth := Round(Pdf.PageWidth * Dpi / 72 * Scale);
DestHeight := Round(Pdf.PageHeight * Dpi / 72 * Scale);
Bitmap := Pdf.RenderPage(0, 0, DestWidth, DestHeight, ro0,
[rePrinting, reAnnotations]);
// ... transfer with StretchDIBits as above
end;
Renderizar a Dpi = 300 é adequado para a maioria das impressoras de escritório. Em 600 DPI, o bitmap para uma única página A4 chega a cerca de 34 megapixels, o que dá cerca de 100 MB como um bitmap de 32 bits; o ganho na qualidade de documentos de texto comuns é mínimo e o custo de memória por página é significativo. Mantenha os 600 DPI para gráficas ou desenhos técnicos ricos em vetores, onde isso genuinamente importa
O sinalizador reAnnotations no segundo bloco de código é independente de rePrinting. Inclua-o quando o usuário esperar que carimbos, destaques e caixas de comentários apareçam no papel. Omita-o para saída apenas de conteúdo. Ambos os sinalizadores podem ser combinados livremente
Rotação de página
O PDFium armazena a rotação de página no PDF como uma entrada /Rotate, acessível por meio de Pdf.PageRotation, a qual retorna um valor TRotation (ro0, ro90, ro180, ro270). O sistema de coordenadas da impressora inverte rotações de 90 e 270 graus em relação à tela. Se você passar o valor bruto de PageRotation diretamente para o RenderPage sem qualquer ajuste, as páginas em paisagem embutidas em um documento retrato serão impressas de cabeça para baixo na maioria dos drivers de impressora do Windows. A correção é uma troca simples antes da chamada de renderização: mapeie ro90 para ro270 e ro270 de volta para ro90, deixando ro0 e ro180 inalterados
Verifique esse comportamento na sua impressora de destino específica antes do envio (shipping). O comportamento do driver com a rotação não é uniforme entre os fornecedores, e alguns drivers aplicam sua própria correção de rotação no nível da GDI. Se você vir uma dupla rotação, remova a troca; se não houver correção alguma, adicione-a. Um documento de orientação mista alternando páginas de retrato e paisagem é a maneira mais rápida de pegar qualquer um desses modos de falha durante os testes
Gerenciamento de memória em um longo trabalho de impressão
Cada chamada a RenderPage aloca um novo TBitmap do qual o chamador é proprietário e deve liberar. No loop acima, o bloco try/finally Bitmap.Free lida com isso corretamente para uma página de cada vez. Não acumule bitmaps ao longo das páginas: a renderização a 300 DPI de um documento de 200 páginas consumiria gigabytes antes de a primeira página chegar ao spooler. Libere cada bitmap antes de avançar para a próxima página
O par AllocMem / FreeMem dentro do bloco de transferência segue a mesma regra. GetDIBSizes lhe diz de quanta memória o cabeçalho DIB e os dados de pixel precisam; você aloca, preenche, pinta e libera tudo dentro do escopo de uma página. Deixar qualquer um dos blocos vazar fará com que o trabalho de impressão esgote a pilha do processo em documentos maiores do que algumas dúzias de páginas
Se você precisar executar trabalhos de impressão em uma thread de plano de fundo, mantenha TPdf e todas as chamadas de impressora VCL na mesma thread. O TPdf por si só não é seguro para threads (thread-safe) entre instâncias que compartilham o estado global da DLL do PDFium; o modelo mais seguro é um TPdf por thread, com cada um carregando sua própria cópia do arquivo
A API de renderização e documento mostrada aqui faz parte do Componente PDFium para Delphi e C++Builder