Gerar um relatório se resume a colocar três coisas em uma página e fazê-las concordar sobre onde ficam: texto em coordenadas conhecidas, fontes que renderizam no servidor igual ao que você vê na sua máquina, e imagens dimensionadas para caber. Tudo o mais que uma biblioteca de relatórios faz se organiza em torno desses três pontos. O HotPDF, a biblioteca de geração de PDF da losLab para Delphi e C++Builder, entrega cada um deles como uma chamada direta no objeto de página, e o único atrito real é saber qual sistema de coordenadas cada chamada fala: as chamadas de desenho usam a mesma orientação de topo esquerdo do canvas da VCL a que você está acostumado, enquanto o arquivo PDF por baixo corre ao contrário. Acerte essa orientação primeiro e o resto do trabalho de layout para de brigar com você
Posicionamento de texto e a origem no canto superior esquerdo
Desenvolvedores que conhecem o formato PDF frequentemente ganham o primeiro relatório HotPDF de cabeça para baixo. Eles subtraem cada Y da altura da página, o título aparece perto da borda inferior e cada linha seguinte sobe em direção ao topo. Nada está com defeito. O user space do PDF, definido na ISO 32000-1 §8.3, de fato coloca a origem no canto inferior esquerdo, com Y crescendo para cima, a imagem espelhada do canvas do GDI, mas as chamadas de desenho de página do HotPDF escondem isso: elas medem a partir do canto superior esquerdo, com Y crescendo para baixo, exatamente como o canvas da VCL, e o THPDFPage.YProjection converte cada coordenada para o user space enquanto o content stream é gravado. Cinco minutos gastos confirmando em qual moldura você está salvam um layout que, de outro modo, você reescreveria assim que os números deixassem de fazer sentido
A chamada central do objeto de página é TextOut(X, Y, Angle, Text). X e Y localizam o texto em pontos a partir do canto superior esquerdo, com o Y marcando a borda superior do texto a menos que você chame TextFromBaseLine(True) para posicionar baselines em vez disso, e Angle o rotaciona em graus, que é como um carimbo diagonal de DRAFT ou COPY é desenhado sem nenhum suporte especial. A intuição treinada na VCL, portanto, funciona sem alterações, porque o Y é simplesmente a distância a partir do topo da página:
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, 50, 0, 'INVOICE'); // 50pt da borda superior
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 70, 0, 'Date: 2026-06-11');
Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY'); // carimbo rotacionado
Pdf.AddPage; // CurrentPage aponta para cá agora
Pdf.CurrentPage.SetFont('Arial', [], 10); // o estado de fonte não é herdado
Pdf.CurrentPage.TextOut(50, 50, 0, 'Page 2 detail rows');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Os dois comportamentos com estado dessa listagem respondem pela maioria dos bugs que só aparecem na página dois. AddPage reaponta CurrentPage para a página que acabou de criar, então uma referência de página que você guardou antes já não desenha onde você espera. A seleção de fonte também é por página, não por documento. Se você pular o SetFont depois de um AddPage, o primeiro TextOut na página nova recai no padrão com que a página começou, e não na fonte de título em negrito que você definiu três páginas atrás. O hábito seguro é tratar "começar uma página nova" e "restabelecer o estado de texto" como um único passo inseparável dentro do laço do relatório
Fontes que existem no servidor, não só na sua máquina
A maioria dos problemas de fonte é, na verdade, um problema de implantação disfarçado. A sua máquina de desenvolvimento tem a fonte corporativa instalada, então o relatório fica certo na sua tela e vai para produção. O servidor de produção roda o serviço com uma conta que nunca teve aquela fonte instalada, o renderizador substitui em silêncio por algo que consegue encontrar, e a primeira notícia que alguém tem disso é um cliente perguntando por que o papel timbrado mudou. A saída é parar de confiar no diretório de fontes do sistema operacional e carregar a fonte de um arquivo que o seu instalador coloca em disco. A chamada de registro Unicode do HotPDF recebe um caminho e faz exatamente isso:
Pdf.RegisterUnicodeTTF('C:\ProgramData\MyApp\Fonts\NotoSans.ttf');
Pdf.CurrentPage.SetFont('NotoSans', [], 12);
Pdf.CurrentPage.TextOut(50, 700, 0, WideString('Łódź - Ünïcode test ✓'));
TextOut aceita uma WideString diretamente, o que importa mais do que parece à primeira vista. Um nome de cliente com acento, uma rua alemã, uma cidade polonesa: esses não são casos extremos, são o conteúdo normal de uma tabela de clientes, e passam pela mesma chamada dos rótulos ASCII que você fixa no código, desde que a fonte registrada contenha de fato os glifos. Uma restrição de versão vem junto com as fontes incorporadas: o documento precisa ser PDF 1.5 ou posterior, então, se um requisito não relacionado estiver prendendo você a uma versão mais antiga, é isso que vai quebrar em silêncio. Escritas da direita para a esquerda, como árabe e hebraico, precisam de shaping de verdade, e não de uma consulta direta de glifos, e isso tem um pipeline próprio; veja nosso artigo sobre shaping de texto de escritas complexas com HotPDF
Quando nenhuma fonte instalada consegue expressar o que você precisa, pense em caracteres MICR em um cheque ou em um conjunto proprietário de símbolos, as fontes Type 3 preenchem a lacuna. Você define cada glifo como um pequeno content stream por meio de RegisterType3Font e AddType3Glyph. É um canto especializado da API e você raramente vai recorrer a ele, mas é bem mais limpo do que espalhar centenas de pequenos bitmaps de símbolo por uma página
Imagens: os argumentos do meio são largura e altura, não um canto
O tratamento de imagens se divide em dois passos, e manter os dois separados é a questão toda. AddImage recebe um TBitmap ou um TJPEGImage, incorpora a imagem uma vez e devolve um índice. Arte em PNG precisa ser decodificada para bitmap antes de chegar lá. ShowImage então desenha esse índice onde e quantas vezes você quiser. A ordem dos argumentos de ShowImage é o único ponto em que vale desacelerar para ler:
var
Png: TPngImage;
Logo: TBitmap;
LogoIdx: Integer;
begin
Png := TPngImage.Create;
Logo := TBitmap.Create;
try
Png.LoadFromFile('brand-logo.png');
Logo.Assign(Png); // decodifica o PNG para um bitmap
LogoIdx := Pdf.AddImage(Logo, icFlate); // sem perdas para arte de cor chapada
finally
Logo.Free;
Png.Free;
end;
// (Index, X, Y, Width, Height, Angle): não (X1, Y1, X2, Y2)
Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;
Os dois números depois da posição são uma largura e uma altura. Não são as coordenadas do canto oposto, e o argumento final é um ângulo de rotação em graus. Leia a assinatura como uma caixa X1/Y1/X2/Y2 e um logotipo de 120 por 40 posicionado em (50, 700) passa a se esticar dali até (120, 40), espalhando-se pela maior parte da página. A saída deixa o erro óbvio enquanto o código-fonte parece perfeitamente razoável, e é isso que faz você perder uma tarde. KeepImageAspectRatio vem como True por padrão, então uma caixa com as proporções erradas aplica bordas à imagem em vez de distorcê-la; mude para False só quando você realmente quiser esticar
A separação entre registrar e posicionar compensa em execuções longas. Como AddImage incorpora os pixels uma vez e todo ShowImage com aquele índice aponta de volta para o mesmo objeto incorporado, onde você chama AddImage decide o tamanho do arquivo. Chame dentro do laço de páginas para um extrato de 500 páginas e o mesmo logotipo é incorporado 500 vezes. Chame uma vez antes do laço, guarde o índice, e o logotipo é armazenado uma única vez. Um pequeno dicionário com chave no caminho do arquivo já basta para garantir que cada imagem distinta seja registrada exatamente uma vez
A escolha do codec é a outra alavanca de tamanho. Conteúdo fotográfico, anexos digitalizados e afins, pertence ao JPEG: passe icJpeg para AddImage e baixe JpegQuality para algo em torno de 85, já que a propriedade começa em 100 e a diferença em 85 é invisível em uma página impressa. Arte de cor chapada, como logotipos, gráficos e desenhos de linha, pertence ao icFlate, em que a compressão sem perdas já é compacta e o JPEG borraria um halo visível em volta das bordas duras. Uma tiragem de extratos que empurra uma foto em qualidade máxima para cada página pode inchar até gigabytes; o mesmo conteúdo em JPEG 85 fica por volta de um décimo do tamanho, e nenhum leitor percebe
Fios, caixas e sombreado com primitivas de caminho
A linha horizontal sob o cabeçalho de uma tabela e a caixa cinza atrás de um valor de total não precisam ser imagens. Desenhe-as como vetores e elas continuam nítidas em qualquer zoom, imprimem com precisão e quase nada acrescentam ao arquivo. O HotPDF segue o mesmo modelo que os content streams de PDF usam: construa um caminho e depois chame um operador que o pinte
// Fio horizontal sob o cabeçalho da tabela
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 132);
Pdf.CurrentPage.LineTo(545, 132);
Pdf.CurrentPage.Stroke;
// Caixa de totais sombreada: X, Y do topo esquerdo, depois largura e altura
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 632, 150, 40);
Pdf.CurrentPage.Fill;
A ordem não é opcional: defina o estado de pintura, construa o caminho e então chame Stroke ou Fill. Um caminho que você constrói mas nunca pinta não contribui em nada para a página, o que quase sempre é a resposta quando um fio "não está aparecendo". SetRGBFillColor recebe um único TColor, então as constantes familiares da VCL, como clNavy e clBlack, entram direto, e Rectangle usa os mesmos argumentos de largura e altura do posicionamento de imagens, e não dois cantos. Um cuidado com linhas finas: qualquer coisa abaixo de meio ponto pode parecer elegante em um monitor e depois sumir em uma impressora de escritório de 600 dpi, então 0,75pt é um piso razoável para qualquer fio que precise sobreviver à impressão
Paginação contra dados reais, não dados de exemplo
Um detalhe a acertar antes de o layout endurecer: colunas numéricas devem ser alinhadas pela borda direita, e a forma de fazer isso é medir a largura renderizada de cada valor e posicioná-lo a partir do limite da coluna, e não preencher a string com espaços à esquerda. O preenchimento com espaços só se alinha em fonte monoespaçada, e ninguém compõe um relatório financeiro em fonte monoespaçada. Passe os valores primeiro pelas rotinas sensíveis à localidade do Delphi, como FormatFloat, para que o separador de milhar cuja largura você mede seja o mesmo que a localidade do cliente vai de fato exibir
O perigo da paginação é que você a escreve contra o conjunto de dados de demonstração, em que dez linhas curtas cabem em uma página e o laço nunca precisa quebrar. A produção entrega um cliente cujo nome de empresa tem 140 caracteres e um extrato com 4.000 itens, e agora o laço precisa quebrar corretamente todas as vezes. O padrão que se sustenta é um único cursor Y que se move para baixo conforme você soma a altura de cada linha, e uma verificação que inicia uma página nova assim que o cursor cruzaria a margem inferior. Para baixo, aqui, significa Y crescente, exatamente como no canvas da VCL; apenas as coordenadas que você lê de volta de um arquivo pronto, como posições de extração de texto, vêm em user space do PDF e contam ao contrário. Mantenha tudo isso em uma rotina que também reemita SetFont e redesenhe o cabeçalho corrente na página nova, e os bugs de uma página a mais ou a menos nunca ganham espaço. Quando os mesmos relatórios também precisam atender a regras de arquivamento ou de acessibilidade, as escolhas que você faz bem aqui, quais fontes incorpora, se a saída é marcada, quais espaços de cor usa, são justamente as que essas normas fiscalizam; o guia de PDF/A, PDF/X e PDF/UA do HotPDF vale a leitura antes de o modelo endurecer
Toda chamada mostrada aqui, o posicionamento de texto, o registro de fontes, a incorporação de imagens e o desenho de caminhos, acompanha o HotPDF Delphi Component para Delphi e C++Builder, cuja referência documenta a API de saída completa junto dos recursos de formulários, criptografia e assinatura ao lado dos quais ela vive