Technical Article

Hiperligações Delphi no HotPDF: Dicas para Anotações PrintHyperlink

As hiperligações PDF são anotações URI: um retângulo que cobre uma determinada área da página que, ao ser clicado, indica ao visualizador para abrir um URL. A anotação e o texto abaixo dela são objetos completamente independentes. O método PrintHyperlink do HotPDF agrupa ambos numa única chamada, desenhando o texto e calculando o retângulo da anotação com base nas métricas do texto renderizado. Esta conveniência esconde um pormenor que vale a pena compreender antes de escrever código de produção.

Como funciona o PrintHyperlink

O PrintHyperlink reside em THPDFPage e aceita quatro argumentos: coordenadas X e Y (em pontos, origem no canto inferior esquerdo, com Y a crescer para cima), a string de etiqueta (label) a desenhar e o URL de destino. Internamente, chama TextOut na cor atual da hiperligação e, em seguida, calcula imediatamente o retângulo da anotação a partir de TextWidth e TextHeight nas métricas de tipo de letra atuais. Isso significa que o tipo de letra e o tamanho têm de ser definidos antes da chamada, e não devem sofrer alterações entre o desenho da etiqueta e a inserção da anotação, porque ambos são resolvidos na mesma chamada.

A cor predefinida é clBlue. O método SetRGBHyperlinkColor altera-a apenas para as chamadas seguintes; não atualiza retroativamente anotações já escritas. Se necessitar de cores diferentes para diferentes grupos de ligações na mesma página, chame SetRGBHyperlinkColor antes de cada grupo e restaure o valor original depois.

Eis um documento mínimo que escreve três ligações com duas cores diferentes:

procedure CreateLinkedReport(const FileName: string);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.BeginDoc;

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

    // Default blue for informational links
    Pdf.CurrentPage.TextOut(50, 750, 0, 'Reference links:');
    Pdf.CurrentPage.PrintHyperlink(50, 720, 'Product page', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
    Pdf.CurrentPage.PrintHyperlink(50, 695, 'Online manual', 'https://www.loslab.com/en-us/pdf-library/documentation.html');

    // Red for the action link
    Pdf.CurrentPage.SetRGBHyperlinkColor(clRed);
    Pdf.CurrentPage.PrintHyperlink(50, 660, 'Purchase license', 'https://www.loslab.com/en-us/order/');
    Pdf.CurrentPage.SetRGBHyperlinkColor(clBlue);  // restore default

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

A armadilha das coordenadas

O HotPDF utiliza uma origem no canto inferior esquerdo com Y a crescer para cima, medida em pontos (1/72 polegada). Uma página A4 mede 595 x 842 pt; uma página US Letter mede 612 x 792 pt. Y=750 situa-se próximo do topo de uma página A4, e Y=50 estaria próximo da margem inferior. Quem vem da área de gráficos de ecrã ou HTML assume o oposto e coloca a primeira linha de ligação diretamente fora da área visível.

O retângulo de anotação que o PrintHyperlink calcula utiliza o mesmo sistema de coordenadas. Se mais tarde rodar a página, a redimensionar ou alterar o tamanho da página sem recalcular os valores X/Y, o texto visível e o retângulo clicável irão afastar-se. A ligação "funciona" no sentido de que clicar em algum local perto do texto ativa o URL, mas a zona ativa deixa de corresponder ao que o leitor vê. Teste no tamanho real da página e no nível de zoom com que irá distribuir o documento, e não apenas na máquina de desenvolvimento a 100%.

Um caso em que o desvio é garantido: se chamar o PrintHyperlink com coordenadas adequadas para uma página A4 e depois mudar para uma página com formato estreito personalizado sem ajustar os valores X/Y, a anotação pode acabar por ficar totalmente fora da página. O objeto da anotação continua a ser escrito no PDF; a maioria dos visualizadores recorta-o silenciosamente, pelo que a ligação simplesmente desaparece sem qualquer erro.

Texto da etiqueta versus URL de destino

Os argumentos Text e Link são independentes. Pode desenhar "Download invoice PDF" enquanto o destino é um URL HTTPS totalmente qualificado com parâmetros de consulta. Esta separação é intencional; a etiqueta visível deve ser legível por humanos e o URL pode ser longo ou gerado dinamicamente.

O que cria problemas é quando a etiqueta é o próprio URL em bruto, especialmente se for longo. Se o URL quebrar visualmente em duas linhas mas o retângulo de anotação tiver sido calculado para uma string de linha única, apenas a primeira linha será clicável. O PrintHyperlink não lida com fluxos multilinha; mantenha a etiqueta curta o suficiente para caber numa única linha no tamanho de letra atual e largura de página, ou utilize uma etiqueta descritiva curta com o URL completo como destino.

Para documentos que serão arquivados ou distribuídos sem uma ligação ativa à internet, considere também se o próprio URL deve aparecer de forma impressa em algum lugar no corpo do documento, e não apenas como metadados de anotação. Um leitor que imprima o PDF em papel não tira qualquer partido de uma anotação URI.

Um exemplo completo de geração de documento

O padrão abaixo mostra um cenário mais realista: gerar um relatório curto com uma secção de cabeçalho, texto do corpo e uma linha de rodapé com ligações, tudo a partir de código em vez de um formulário com campos TEdit:

procedure GenerateProductSheet(
  const FileName, ProductName, ProductURL, SupportURL: string);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Compression := cmFlateDecode;
    Pdf.BeginDoc;

    // Header
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));

    // Body paragraph placeholder
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');

    // Footer links
    Pdf.CurrentPage.SetFont('Arial', [], 10);
    Pdf.CurrentPage.TextOut(50, 80, 0, 'Links:');
    Pdf.CurrentPage.PrintHyperlink(50, 60, 'Product page', ProductURL);
    Pdf.CurrentPage.PrintHyperlink(200, 60, 'Support', SupportURL);

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Note que o SetFont é chamado antes de cada grupo de chamadas de texto. O tipo de letra não persiste após a chamada a AddPage, e se se esquecer de o definir antes de PrintHyperlink numa página nova, o retângulo de anotação será calculado com base nas métricas predefinidas da página, que podem diferir do que espera.

Onde a gestão de anotações varia entre visualizadores

As anotações URI em PDF são definidas na ISO 32000-1 §12.6.4.7, e todos os visualizadores conformes as devem seguir. Na prática, alguns comportamentos diferem de visualizador para visualizador. O Adobe Acrobat exibe um aviso de segurança no primeiro clique para URLs que não estejam na lista de domínios fidedignos; muitos navegadores e leitores leves não o fazem. Alguns visualizadores de PDF empresariais em ambientes restritos desativam por completo as anotações URI através de políticas de segurança, pelo que o clique nada faz, sem qualquer erro visível. As aplicações móveis de PDF variam quanto a abrir as ligações dentro da vista web da aplicação ou passá-las para o navegador do sistema.

Nenhum deste problemas são bugs que possa corrigir do lado da geração; são decisões de política do visualizador. O que pode fazer é escrever etiquetas de ligação que também tornem o URL visível no corpo do documento, para que um leitor num ambiente restrito possa copiar o endereço manualmente. A anotação é a conveniência; o texto é a alternativa de recurso.

Outro detalhe que vale a pena saber: as anotações URI em PDF não possuem qualquer sublinhado visual por defeito. O sublinhado que vê na maioria dos visualizadores é desenhado pelo próprio programa com base no tipo de anotação, e não por um glifo no fluxo de conteúdo. Se necessitar de um sublinhado físico que sobreviva à impressão num renderizador não interativo ou à conversão de PDF para imagem, desenhe-o explicitamente com LineTo e Stroke no offset Y adequado abaixo da linha de base do texto. Trata-se de uma operação de desenho separada, que o PrintHyperlink não trata por si.

A API de hiperligações apresentada aqui faz parte do HotPDF Component para Delphi e C++Builder.