Artigo Técnico

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

Toda string visível em um documento HotPDF chega por meio de uma única chamada: TextOut(X, Y, angle, Text). O exemplo do Hello World a usa da forma mais simples, fonte configurada uma vez e quatro argumentos deixados em padrões sensatos. Passada aquela primeira página, os mesmos quatro argumentos carregam todo o peso do layout. O terceiro argumento rotaciona a execução. A fonte configurada logo antes dele decide o tamanho e o estilo. E o par X, Y, medido a partir do canto da página em pontos, é a única coisa que fica entre um relatório limpo e um texto que se sobrepõe, corta ou flutua uma linha mais abaixo na impressora de outra pessoa. É aqui que o TextOut prova o seu valor e onde os padrões deixam de ser suficientes

Vale a pena fixar a assinatura na mente antes de qualquer outra coisa: X e Y são Single em pontos, angle é um Extended em graus e Text é uma WideString, de modo que o Unicode passa sem uma chamada separada. Uma segunda sobrecarga leva um PWORD mais um comprimento, para quando você já possui códigos de glifos, mas, para strings comuns, o formulário WideString é aquele ao qual você recorre

O tamanho e o estilo vêm do SetFont, não do TextOut

O TextOut não tem nenhum parâmetro de tamanho. O tamanho, o peso, a inclinação, tudo isso vive na chamada do SetFont que antecede a execução e permanece em vigor até que o próximo SetFont a substitua. Esse é o único fato que explica a maior parte da confusão do primeiro dia: uma linha sai em negrito porque três chamadas antes algo definiu [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, então [fsBold, fsItalic] é negrito itálico e [] é simples. O tamanho é em pontos, a mesma unidade que as coordenadas, o que torna o espaçamento vertical fácil de raciocinar: uma linha de 12 pontos quer um passo vertical de aproximadamente 14 a 16 pontos para respirar, então a redução do Y em 14 por linha é um avanço inicial razoável (leading). Não há avanço de linha automático. Você mesmo calcula cada linha de base, o que é tedioso para um parágrafo, mas exato para um formulário, onde cada campo se localiza numa coordenada fixa

Duas notas práticas sobre o nome da fonte. Ele é resolvido em relação às fontes instaladas na máquina de construção, e o que quer que o SO devolva é o que fica embutido, então um nome que é resolvido em sua área de trabalho e um nome que é resolvido em um servidor de construção não são garantidamente a mesma face. Além disso, a fonte tem de abranger as escritas da string. Uma execução de texto cirílico ou CJK sob uma face apenas latina renderiza como caixas de glifos perdidos, sem nenhum erro, razão pela qual a página do Hello World procura por uma ampla face Unicode quando ela mistura idiomas

Página do HotPDF TextOut mostrando Arial, Times New Roman e Courier New renderizadas com estilos regular, negrito e itálico através de vários conjuntos de caracteres

O argumento de ângulo rotaciona em torno da âncora

O terceiro argumento é aquele que a maior parte dos códigos deixa em zero para sempre. Se você passar um valor diferente de zero, a execução girará no sentido anti-horário ao redor de sua própria âncora (X, Y), no canto inferior esquerdo do texto, na quantidade de graus. A própria âncora não se move, portanto, a mesma coordenada que colocou uma legenda horizontal colocará seu gêmeo girado; apenas a direção em que os glifos marcham muda

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 comum, um rótulo subindo pela lateral de um gráfico ou título da lombada. Quarenta e cinco graus lida com cabeçalhos de coluna inclinados, o truque que permite que um rótulo largo fique sobre uma coluna estreita sem se derramar nas de seus vizinhos. A rotação não muda como a âncora é interpretada, o que confunde as pessoas: uma execução de 90 graus ainda começa em (X, Y) e cresce para cima a partir daí, portanto, para centralizar um rótulo rotacionado, você ajusta a âncora, e não o ângulo. Quando várias execuções giradas compartilharem uma linha de base, dê a elas o mesmo Y e mova em X, exatamente como faria com o Y para linhas horizontais empilhadas

Posicionando coordenadas sem adivinhar

Coordenadas são a parte que sobrevive a análises ou falha discretamente. O HotPDF faz as medições a partir do canto inferior esquerdo da página, com Y crescendo para cima, em pontos a 72 para cada polegada. Uma página Letter dos EUA tem 612 por 792 pontos; A4, 595 por 842. Uma margem superior de uma polegada na Letter coloca assim sua primeira linha de base próxima de Y = 792 menos 72 menos o tamanho da fonte, e não em algum número pequeno perto do topo. Quem chega das coordenadas da tela, nas quais Y cresce para baixo a partir do zero, escreve a primeira linha fora da margem inferior e passa dez minutos imaginando para onde ela foi

Trate o layout como aritmética em relação às âncoras nomeadas em vez de uma coluna de números mágicos. Uma margem à esquerda, uma linha de base rotineira que você decrementa a cada linha, bem como um espaçamento fixo, transformam um bloco de rótulos em um loop curto em vez de uma parede de números literais:

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 proteção contra quebra de página é a linha que todo mundo esquece primeiro e aquela onde o campo mais acerta em cheio. Não há layout de fluxo sob o TextOut. Se você decrementar além da margem inferior, o texto continuará a desenhar na medianiz, fora da página, no nada e sem nenhum aviso. Portanto, você mesmo observa Y, chama AddPage ao atravessar o piso, e reinicia a linha de base. O SetFont posterior ao AddPage não é preenchimento opcional: a fonte atual não sobrevive a uma quebra de página, e a primeira execução na nova página sairá com a fonte predefinida do visualizador se você o pular

Espaçamento entre caracteres e palavras para ajuste e alinhamento

Às vezes uma string está correta, mas com a largura errada: um cabeçalho que deve alcançar uma régua fixa, um código que precisaria ser lido com dígitos mais aerados, uma coluna que necessita dos seus valores empurrados ao alinhamento. O PDF comporta dois operadores de estado de texto para isto: espaçamento de caracteres (Tc, espaço a mais acrescentado depois de todo glifo) e espaçamento entre palavras (Tw, espaço extra introduzido em cada caractere de espaço), e ambos são indicados nas unidades não mensuráveis de espaço de texto, de fato pontos de dimensão segundo o atual tamanho da fonte. Eles são o estado, e não parâmetros para TextOut; portanto, você deve fixar, desenhar e os estabelecer de volta

// 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 entre as palavras somente influencia os caracteres de espaço (código 32), o qual possui uma consequência valendo conhecimento: ele faz nada dentro da rodagem de um CJK em que inexistem espaços ASCII, ademais de interatuar incomumente ao passo da codificação do texto quais os códigos de índices e não em bytes. Em saídas da grade em Latim constitui-se o baixo custo da dilatação de vãos livres dispensando tornar a grafar todo texto contíguo. O espaçamento dos caracteres trata-se de um modo muito mais aconselhável se você deseja abranger num apelo toda uma amplitude, estirando um acerto equivalente sobrepondo, assim, a todo glifo em lugar apenas das falhas adjuntas aos termos

A redefinição é a disciplina inteira. O espaçamento, como a fonte, é parte do estado de desenho da página, e o estado persiste até você mudá-lo. Coloque espaçamento entre letras em um cabeçalho e esqueça de zerá-lo, e todo parágrafo abaixo herdará a extensão, que é lida como uma incorreção sutil e difícil de localizar que sobrevive a uma revisão casual e reprova em uma revisão cuidadosa. O hábito confiável é definir um valor de espaçamento, desenhar a execução que precisa dele e defini-lo de volta a zero na próxima linha, para que nenhum código posterior precise saber o que uma seção anterior fez

Página do HotPDF TextOut comparando dimensionamento de texto horizontal, espaçamento de caracteres, espaçamento de palavras e modos de renderização de preenchimento em oposição ao de traço

Verificando a saída onde ela realmente quebra

O layout de texto falha na segunda máquina, não na primeira, então as verificações que importam acontecem longe da sua mesa. Abra o arquivo gerado em um sistema sem a sua fonte de desenvolvedor instalada e confirme se as faces embutidas ainda são renderizadas, incluindo o latim acentuado, qualquer script não latino e a pontuação, em uma única passagem em vez de verificar por amostragem os caracteres fáceis. Selecione e copie algumas linhas para confirmar se o texto é texto real e não contornos, o que importa no momento em que a pesquisa ou extração estiverem no escopo. Forneça ao layout dados representativos, o rótulo em alemão mais longo e o número mais largo, não um espaço reservado (placeholder) arrumado, porque a execução que transborda de um campo é sempre aquela que você não digitou manualmente. E se a página tiver que ser colocada em um formulário pré-impresso, imprima ou rasterize uma amostra e coloque-a contra o original; um desvio de linha de base de um quarto de milímetro é invisível na tela e óbvio no papel

Se você ainda não escreveu uma única página, comece com o exemplo Hello World do HotPDF, que configura o documento, a fonte e o sistema de coordenadas inferior esquerdo, do qual tudo o que está acima depende. As chamadas TextOut, SetFont e de espaçamento mostradas aqui fazem parte do HotPDF Component para Delphi e C++Builder