Technical Article

HotPDF TextOut em Delphi: Tamanho, Estilo, Rotação e Espaçamento

Todas as strings visíveis num documento HotPDF são geradas através de uma única chamada: TextOut(X, Y, angle, Text). O exemplo Hello World utiliza esta função na sua forma mais simples, com a fonte configurada uma vez e os quatro argumentos mantidos nos valores predefinidos padrão. Além dessa primeira página, estes mesmos quatro argumentos suportam todo o peso do layout. O terceiro argumento roda o texto. A fonte definida imediatamente antes determina o tamanho e o estilo. E o par X, Y (medido em pontos a partir do canto da página) é a única barreira que impede que um relatório limpo apresente texto sobreposto, cortado ou deslocado para uma linha inferior na impressora de outra pessoa. É aqui que o TextOut prova a sua utilidade e onde os valores predefinidos deixam de ser suficientes.

A assinatura do método merece ser memorizada antes de mais: X e Y são do tipo Single em pontos, angle é um Extended em graus e Text é uma WideString, o que permite a passagem de Unicode sem necessidade de uma chamada separada. Uma segunda sobrecarga aceita um PWORD acompanhado de um comprimento para os casos em que já possui códigos de glifos, mas para strings comuns a forma com WideString é a mais indicada.

O tamanho e o estilo provêm de SetFont, não de TextOut

O TextOut não tem nenhum parâmetro de tamanho. O tamanho, a espessura e a inclinação pertencem à chamada SetFont que antecede o texto, permanecendo em vigor até que o próximo SetFont a substitua. Este facto explica a maioria das dúvidas iniciais: uma linha surge a negrito porque três chamadas antes foi definido o estilo [fsBold] e nada o limpou.

Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report');        // 24pt regular

Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue');                 // 12pt bold

Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands');    // 11pt italic

Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, '  +18.4% YoY');            // styles combine

O segundo argumento é um conjunto TFontStyles, pelo que [fsBold, fsItalic] representa negrito itálico e [] regular. O tamanho é indicado em pontos (a mesma unidade das coordenadas), o que facilita o cálculo do espaçamento vertical: uma linha de 12 pontos requer cerca de 14 a 16 pontos de avanço vertical para respirar, pelo que reduzir o Y em 14 pontos por linha constitui um ponto de partida razoável para a entrelinha (leading). Não existe avanço automático de linha. Deve calcular cada linha de base manualmente, o que se torna trabalhoso para um parágrafo mas extremamente preciso para um formulário, onde cada campo reside numa coordenada fixa.

Duas notas práticas sobre o nome da fonte. Este é resolvido com base nas fontes instaladas na máquina de compilação, e o que o sistema operativo retornar é o que será incorporado, pelo que um nome que funciona no seu ambiente de trabalho pode não corresponder ao mesmo tipo de letra num servidor de integração. Além disso, a fonte tem de cobrir os caracteres presentes na string. Um segmento de texto em cirílico ou CJK sob uma fonte exclusivamente latina será desenhado como caixas de glifos em falta, sem apresentar erros, razão pela qual o exemplo Hello World recorre a uma fonte Unicode abrangente ao misturar idiomas.

Página HotPDF TextOut que mostra Arial, Times New Roman e Courier New renderizadas com estilos regular, negrito e itálico em vários conjuntos de caracteres

O argumento de ângulo roda em torno do ponto de ancoragem

O terceiro argumento é aquele que a maioria do código deixa a zero para sempre. Se passar um valor diferente de zero, o texto roda no sentido contrário ao dos ponteiros do relógio em torno do seu próprio ponto de ancoragem (X, Y), o canto inferior esquerdo do texto, pelo número correspondente de graus. A própria âncora não se move, pelo que a mesma coordenada que posicionava uma etiqueta horizontal posiciona a sua versão rodada; altera-se apenas a direção em que os glifos se estendem.

Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// A vertical axis label down the left margin: 90 degrees reads bottom-to-top.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// A diagonal DRAFT watermark across the page body.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// Column headers tilted 60 degrees so long labels fit a narrow table.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');

Noventa graus é o caso mais comum, como uma etiqueta disposta ao longo de um gráfico ou o título na lombada de um livro. Quarenta e cinco graus servem para inclinar os cabeçalhos das colunas, um truque que permite colocar uma etiqueta comprida sobre uma coluna estreita sem invadir o espaço das colunas vizinhas. A rotação não altera a interpretação do ponto de ancoragem, o que pode induzir em erro: um texto a 90 graus continua a começar em (X, Y) e cresce para cima a partir daí, pelo que, para centrar uma etiqueta rodada, deve ajustar a âncora e não o ângulo. Quando vários textos rodados partilham uma linha de base, atribua-lhes o mesmo Y e avance o X, tal como avançaria o Y para linhas horizontais empilhadas.

Posicionar coordenadas sem adivinhações

As coordenadas são a parte que dita o sucesso ou o fracasso silencioso de uma revisão. O HotPDF mede a partir do canto inferior esquerdo da página, com o Y a crescer para cima, em pontos (72 por polegada). Uma página US Letter mede 612 por 792 pontos; a A4 tem 595 por 842. Uma margem superior de uma polegada na página Letter coloca, portanto, a primeira linha de base perto de Y = 792 menos 72 menos o tamanho da fonte, e não num número pequeno perto do topo. Quem vier habituado a coordenadas de ecrã (onde o Y cresce para baixo a partir do zero) desenhará a primeira linha abaixo do limite inferior da página e perderá dez minutos a tentar perceber onde ela foi parar.

Trate o layout como aritmética baseada em pontos de ancoragem nomeados, em vez de uma lista de números mágicos. Uma margem esquerda, uma linha de base dinâmica que decresce por linha e um valor de entrelinha fixo transformam um bloco de etiquetas num ciclo curto, em vez de uma sucessão de valores fixos:

const
  LeftMargin = 72;        // 1 inch in
  TopBaseline = 720;       // first line, ~1 inch down on Letter
  Leading = 16;            // vertical step between lines
var
  Y: Single;
  Line: string;
begin
  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Y := TopBaseline;
  for Line in ReportLines do
  begin
    Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
    Y := Y - Leading;
    if Y < 72 then            // bottom margin reached
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // font resets on a new page
      Y := TopBaseline;
    end;
  end;
end;

A verificação da quebra de página é a linha que todos se esquecem primeiro e a que mais impacto tem na prática. Não existe um layout de fluxo automático sob o TextOut. Se decrementar além da margem inferior, o texto continuará a desenhar-se na margem de encadernação, fora da página, no vazio, sem qualquer aviso. Desta forma, deve gerir o Y manualmente, chamar AddPage quando este ultrapassar o limite inferior e repor a linha de base. O SetFont após AddPage não é redundante: a fonte atual não sobrevive a uma quebra de página, e o primeiro texto na nova página será desenhado com a tipografia padrão do visualizador caso ignore este passo.

Espaçamento de caracteres e de palavras para ajuste e alinhamento

Por vezes, a string está correta mas tem a largura errada: um cabeçalho que precisa de ocupar uma largura exata, um código cujos algarismos beneficiam de maior legibilidade se estiverem mais espaçados ou uma coluna cujos valores requerem um ajuste para alinhamento. O PDF dispõe de dois operadores de estado de texto para esta finalidade: character spacing (Tc, espaço extra adicionado após cada glifo) e word spacing (Tw, espaço extra adicionado a cada caractere de espaço), ambos expressos em unidades não dimensionadas do espaço de texto (efetivamente pontos no tamanho atual da fonte). Estes operadores são propriedades de estado, não argumentos do TextOut, pelo que deve defini-los, desenhar e depois repor os seus valores originais.

// Letter-space a short heading so it stretches across a rule.
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 740, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0);   // reset before normal body text

// Open up the gaps between words on a single wide line.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name        Department        Extension');
Pdf.CurrentPage.SetWordSpacing(0);

O espaçamento de palavras (word spacing) atua apenas sobre o caractere de espaço (código 32), o que tem uma consequência importante: não produz efeito num texto CJK que não contenha espaços ASCII e interage de forma invulgar com texto codificado como índices de glifos em vez de bytes. Para tabelas em latim, é uma forma simples de alargar os espaços sem reescrever a string. O espaçamento de caracteres (character spacing) é a ferramenta ideal para cabeçalhos que devem atingir uma largura específica, uma vez que distribui o ajuste de forma uniforme por cada glifo, em vez de o concentrar nos espaços.

A reposição dos valores originais constitui a regra de ouro desta disciplina. O espaçamento, tal como a fonte, faz parte do estado de desenho da página, e o estado persiste até que seja modificado. Se aplicar o espaçamento num cabeçalho e se esquecer de o repor a zero, todos os parágrafos seguintes herdarão esse estiramento, resultando numa estranheza subtil mas difícil de identificar que escapa a uma revisão superficial mas falha numa leitura atenta. O hábito recomendado consiste em definir o valor de espaçamento, desenhar o texto pretendido e repô-lo a zero na linha seguinte, para que o código posterior não seja afetado pelo que foi feito na secção anterior.

Página HotPDF TextOut que compara o dimensionamento horizontal do texto, o espaçamento de caracteres, o espaçamento de palavras e os modos de renderização de preenchimento versus traço

Validar o resultado onde este costuma falhar

O esquema de texto falha no computador do cliente, não no do programador, pelo que as verificações essenciais devem ocorrer fora do seu ambiente de trabalho. Abra o ficheiro gerado num sistema que não possua as suas fontes de desenvolvimento instaladas e confirme que as fontes incorporadas são renderizadas corretamente (incluindo caracteres latinos acentuados, quaisquer scripts não latinos e pontuação), numa única validação em vez de testar apenas os caracteres mais comuns. Selecione e copie algumas linhas para confirmar que o texto corresponde a texto real e não a contornos (outlines), o que é fundamental para fins de pesquisa ou extração. Teste o layout com dados representativos (a palavra em alemão mais comprida e o maior número possível, e não um espaço reservado curto), pois o texto que excede um campo é sempre aquele que não foi introduzido manualmente. Se a página tiver de coincidir com um formulário pré-impresso, imprima ou rasterize uma amostra e sobreponha-a à original; um desvio de um quarto de milímetro na linha de base é invisível no ecrã mas evidente no papel.

Se ainda não gerou nenhuma página, comece pelo exemplo HotPDF Hello World, que configura o documento, a fonte e o sistema de coordenadas de canto inferior esquerdo em que todas as operações descritas assentam. As chamadas de TextOut, SetFont e de espaçamento aqui demonstradas fazem parte do HotPDF Component para Delphi e C++Builder.

phi-pdf-component.html" rel="noopener" target="_blank">HotPDF Component for Delphi and C++Builder.