Artigo Técnico

Relatórios PDF em Delphi com HotPDF: TextOut, Tipos de Letra e Imagens

Texto, tipos de letra e coordenadas no relatório

Um relatório PDF em Delphi começa por três decisões simples: onde colocar o texto, que tipo de letra garante o mesmo aspeto no servidor e na máquina de desenvolvimento, e como inserir imagens sem aumentar inutilmente o ficheiro. O HotPDF expõe estas operações diretamente no objeto de página, mas obriga a pensar no sistema de coordenadas do PDF, cuja origem fica no canto inferior esquerdo

A chamada TextOut recebe X, Y, ângulo e texto. Para quem vem do canvas VCL, o ponto que causa erros é o eixo Y crescer para cima. Uma regra prática é calcular Y como altura da página menos a distância pretendida ao topo. Assim o modelo mental continua próximo do desenho em ecrã, mas o resultado obedece ao espaço do PDF

Estado da página e seleção de tipos de letra

Depois de AddPage, CurrentPage passa a apontar para a nova página e o estado de texto deve ser reposto. Reemitir SetFont no início de cada página é uma disciplina simples que evita cabeçalhos com o tipo errado, linhas de detalhe no tamanho anterior ou páginas seguintes desenhadas com valores herdados por acidente

Os problemas de tipos de letra quase nunca são problemas de desenho, mas de instalação. Se a fonte corporativa existe apenas no computador do programador, a execução em serviço pode substituí-la sem aviso. Registar um ficheiro TTF com RegisterUnicodeTTF e distribuí-lo com a aplicação torna o resultado previsível. Texto Unicode deve entrar como WideString, mantendo nomes, acentos e símbolos no mesmo fluxo que as legendas ASCII

Imagens e reutilização de recursos

A inserção de imagens tem duas fases. AddImage embebe os pixels uma vez e devolve um índice. ShowImage posiciona esse índice na página com X, Y, largura, altura e rotação. A largura e a altura não são o canto oposto da caixa, e confundir esta assinatura é uma das causas clássicas de logótipos esticados ou deslocados

Registar uma imagem dentro do ciclo de páginas repete o mesmo conteúdo em cada página e multiplica o tamanho do PDF. Registe cada recurso uma vez, guarde o índice numa tabela por caminho de ficheiro e reutilize esse índice. Para fotografias use JPEG com qualidade controlada; para logótipos, gráficos e arte plana, prefira compressão lossless para evitar artefactos

Vetores, linhas e paginação

Linhas horizontais, caixas de totais e fundos de secção devem ser desenhados como vetores. MoveTo, LineTo, Rectangle, Stroke e Fill produzem conteúdo nítido em qualquer zoom e quase não aumentam o tamanho do ficheiro. A ordem continua importante: definir o estado de pintura, construir o caminho e só depois chamar Stroke ou Fill

A paginação deve ser testada com dados reais, não apenas com dez linhas curtas de demonstração. Mantenha um cursor Y, subtraia a altura de cada linha e crie nova página antes de atravessar a margem inferior. A rotina que cria a página também deve repor o tipo de letra, redesenhar o cabeçalho e preservar as escolhas de layout que tornam o relatório consistente

Verificação antes de publicar

Antes de entregar o gerador, abra o PDF em mais do que um leitor, imprima uma página com linhas finas, confirme a incorporação das fontes e verifique o tamanho final com imagens reais. Este teste simples encontra diferenças entre ambiente de desenvolvimento, serviço de produção e visualizadores externos antes de o cliente as encontrar

Exemplos de código adicionais

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-0001.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(50, 792 - 50, 0, 'INVOICE');       // 50pt from top of Letter
    Pdf.CurrentPage.SetFont('Arial', [], 10);
    Pdf.CurrentPage.TextOut(50, 792 - 70, 0, 'Date: 2026-06-11');
    Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY');              // rotated stamp
    Pdf.AddPage;                                                // CurrentPage now points here
    Pdf.CurrentPage.SetFont('Arial', [], 10);                   // font state does not carry over
    Pdf.CurrentPage.TextOut(50, 742, 0, 'Page 2 detail rows');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;
Pdf.RegisterUnicodeTTF('C:\ProgramData\MyApp\Fonts\NotoSans.ttf');
Pdf.CurrentPage.SetFont('NotoSans', [], 12);
Pdf.CurrentPage.TextOut(50, 700, 0, WideString('Łódź - Ünïcode test ✓'));
var
  Png: TPngImage;
  Logo: TBitmap;
  LogoIdx: Integer;
begin
  Png := TPngImage.Create;
  Logo := TBitmap.Create;
  try
    Png.LoadFromFile('brand-logo.png');
    Logo.Assign(Png);                       // decode PNG to a bitmap
    LogoIdx := Pdf.AddImage(Logo, icFlate); // lossless for flat-color art
  finally
    Logo.Free;
    Png.Free;
  end;
  // (Index, X, Y, Width, Height, Angle): not (X1, Y1, X2, Y2)
  Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;
// Horizontal rule under the table header
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 660);
Pdf.CurrentPage.LineTo(545, 660);
Pdf.CurrentPage.Stroke;

// Shaded totals box: X, Y, width, height
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 120, 150, 40);
Pdf.CurrentPage.Fill;