Technical Article

Anotações PDF em Delphi com o HotPDF: Tipos e Retângulos

Uma anotação não constitui conteúdo da página. Quando chama o TextOut ou desenha um retângulo, os traços passam a fazer parte do fluxo de conteúdo da página, integrados nos bytes que o renderizador desenha. Uma anotação é um dicionário independente associado à página através do seu array /Annots, com o seu próprio retângulo, aparência e ciclo de vida. O visualizador pode abri-la, movê-la, ocultá-la ou removê-la sem afetar um único glifo da página subjacente. Esta separação é a razão de ser das anotações e constitui também a origem de dois aspetos que costumam surpreender os programadores ao início: onde a anotação se posiciona e qual o seu aspeto visual consoante o visualizador de PDF utilizado.

O HotPDF disponibiliza os subtipos de anotação da norma ISO 32000 através de uma família de chamadas AddXxxAnnotation no objeto da página. Todas partilham a mesma estrutura: um retângulo que fixa a anotação na página no espaço de utilizador PDF, o conteúdo útil (texto, nome do carimbo, par de pontos) e uma cor. Ao definir corretamente o retângulo, a maior parte do trabalho fica concluída. O resto consiste em saber quais os subtipos que transportam a sua própria aparência e quais os que dependem do visualizador para serem desenhados.

Uma página PDF gerada pelo HotPDF exibindo ícones de notas de texto, caixas de texto livre, marcações geométricas e de linha, e carimbos de aprovação distribuídos pela página
Uma página com vários subtipos de anotação em simultâneo: notas de texto, texto livre, marcações geométricas e carimbos

O retângulo define a anotação, e não o texto

Cada chamada de anotação recebe um TRect, e esse retângulo tem um significado diferente das coordenadas passadas para o TextOut. Numa nota de texto, representa o ponto de interação (hotspot) clicável, a pequena área onde o ícone da nota se situa e onde um clique abre o comentário. Numa caixa de forma quadrada ou de texto livre, define a extensão visível da marcação. Num carimbo, é a caixa onde a ilustração do carimbo é redimensionada. Os números correspondem a pontos no espaço de utilizador do PDF, medidos a partir do canto inferior esquerdo da página com Y a crescer para cima, a mesma convenção utilizada no restante ecossistema do HotPDF.

A nota de texto é o subtipo mais simples. Fornece-lhe o texto do corpo, um retângulo para o ícone, um indicador se abre por defeito, o nome do ícone e uma cor.

Pdf.CurrentPage.AddTextAnnotation(
  'Reviewer: confirm the totals on this line before sign-off.',
  Rect(120, 700, 140, 720),   // icon hotspot, ~20pt square
  False,                      // closed until the reader clicks it
  taComment,                  // bubble icon
  clBlue);

O retângulo aqui é intencionalmente pequeno, com cerca de vinte pontos de lado, porque uma nota de texto consiste apenas num ícone até que alguém clique nele. Se definir um retângulo grande, não obterá uma nota maior; obterá sim uma área de clique excessiva com o ícone fixado num dos cantos. O parâmetro Open controla se a janela pop-up é exibida quando o documento é carregado. Definir várias notas como True fará com que se sobreponham umas às outras e ao conteúdo, pelo que deve reservar esta opção apenas para a nota que pretende que o leitor veja de imediato.

O nome do ícone provém de THPDFTextAnnotationType, que mapeia para os ícones padrão de notas: taComment, taKey, taNote, taHelp, taParagraph, taNewParagraph e taInsert. O ícone é a única característica que o tipo altera. Não modifica o comportamento do elemento e convém notar que nem todos os visualizadores renderizam os sete ícones; as opções seguras em leitores antigos e recentes são o taComment, taNote e taHelp.

O texto livre escreve na página, mas permanece como anotação

Uma anotação de texto livre assemelha-se a conteúdo da página porque o texto fica visível sem necessidade de clique, posicionado no seu retângulo como uma legenda. Contudo, continua a ser uma anotação, com toda a independência que isso implica, o que é ideal para um carimbo de revisão ou uma etiqueta de rascunho que se pretenda remover mais tarde. A assinatura da chamada substitui o ícone e o indicador de abertura por um valor de alinhamento.

Pdf.CurrentPage.AddFreeTextAnnotation(
  'DRAFT - not for distribution',
  Rect(200, 210, 400, 235),   // the box the text is laid into
  ftCenter,                   // ftLeftJust / ftCenter / ftRightJust
  clRed);

Aqui o retângulo assume maior importância do que numa nota de texto, dado que o texto é quebrado e alinhado no seu interior. Se dimensionar a caixa com pouca altura, o texto será cortado no limite inferior; se for muito estreita, quebrará em pontos indesejados. O alinhamento provém de THPDFFreeTextAnnotationJust e possui apenas três valores. Como o texto livre é uma anotação de marcação, o utilizador que abrir o ficheiro num editor pode selecioná-lo, movê-lo ou eliminá-lo como um bloco, sendo esta a diferença que determina se deve optar por texto livre ou desenhar simplesmente as palavras com o TextOut. Se a etiqueta deve ser permanente, desenhe-a. Se for editorial e destinada a ser removida, configure-a como anotação.

Marcações geométricas e de linha para sinalizar elementos

Quadrados, círculos e linhas são as marcações utilizadas para apontar para uma área específica, em vez de a descrever textualmente. O AddCircleSquareAnnotation abrange as duas formas geométricas através de um THPDFCSAnnotationType configurado como csCircle ou csSquare, em que o retângulo define os limites da forma.

// A box drawn around a figure that needs attention
Pdf.CurrentPage.AddCircleSquareAnnotation(
  'Check this region against the source data',
  Rect(50, 300, 120, 360),
  csSquare,
  clGreen);

// A line, given two points rather than a rectangle
var
  StartPt, EndPt: THPDFCurrPoint;
begin
  StartPt.X := 130; StartPt.Y := 360;
  EndPt.X   := 250; EndPt.Y   := 320;
  Pdf.CurrentPage.AddLineAnnotation(
    'Points from the note to the figure',
    StartPt, EndPt,
    clBlue);
end;

Observe que a anotação de linha não segue o padrão do retângulo: recebe dois registos THPDFCurrPoint, um de início e outro de fim, uma vez que a linha é definida pelos seus pontos extremos e não por uma caixa delimitadora. A cor define o traço. Se pretender pontas de seta, o HotPDF dispõe de sobrecargas do AddLineAnnotation que aceitam estilos de terminação de linha, mas a versão simples com três argumentos desenha uma linha simples, o que costuma ser suficiente para uma sinalização.

Os subtipos de marcação de texto atuam sobre uma área já estruturada. O AddHighlightAnnotation recebe um retângulo, conteúdo opcional e uma cor (que assume amarelo por defeito) e colore a área da mesma forma que um marcador de texto. Destina-se a sobrepor-se a texto real, pelo que o retângulo deve coincidir com os limites das palavras desenhadas, o que significa que geralmente deve calculá-lo a partir das coordenadas passadas para o TextOut em vez de tentar adivinhar.

Os carimbos dependem do visualizador para serem renderizados

A anotação de carimbo é a que mais frequentemente apresenta diferenças entre leitores, e o motivo é importante. O AddStampAnnotation define um carimbo padrão através de THPDFStampAnnotationType, com valores como satApproved, satConfidential, satFinal, satDraft e satForComment.

Pdf.CurrentPage.AddStampAnnotation(
  'Approved for release on review',
  Rect(50, 400, 200, 440),
  satApproved,
  clGreen);

O nome do carimbo funciona como uma solicitação. O formato PDF define o conjunto de nomes de carimbo padrão, mas não as ilustrações associadas aos mesmos, pelo que cada visualizador disponibiliza o seu próprio desenho para "APPROVED" ou "CONFIDENTIAL", e alguns não renderizam nada para nomes que não reconheçam. O retângulo controla a caixa onde a ilustração é dimensionada e a cor funciona como uma sugestão que o visualizador pode ou não respeitar. Se necessitar que o carimbo tenha um aspeto idêntico em qualquer plataforma, o caminho seguro consiste em evitar o carimbo padrão: desenhe o grafismo com TextOut e primitivas de desenho, ou utilize uma anotação de texto livre controlando a sua aparência. Opte pelo carimbo padrão quando preferir a imagem habitual do visualizador e puder tolerar pequenas variações.

Os anexos de ficheiros seguem o mesmo formato de retângulo e conteúdo útil. O AddFileAttachmentAnnotation recebe a descrição, o caminho do ficheiro a incorporar, um retângulo para o ícone de clipe e uma cor. O ficheiro é integrado no PDF e o ícone funciona como o ponto de acesso que o leitor utiliza para o extrair.

Como as anotações diferem dos campos AcroForm

O equívoco que consome mais tempo de desenvolvimento é tratar uma anotação como se fosse um campo de formulário. Ambos associam-se à página através de /Annots, e un campo de formulário é, na verdade, um subtipo especial de anotação (um widget), razão pela qual parecem relacionados. Contudo, não são intercambiáveis. Um campo de formulário armazena um valor, tem um nome, participa na ordem de tabulação e pode ser submetido, limpo ou conter scripts; a criação destes elementos faz-se através de chamadas como AddTextField, AddCheckBox e AddPushButton, e não pelas chamadas de anotação desta página. Uma anotação de marcação guarda apenas um comentário ou forma, não possui qualquer valor para submissão e é a ferramenta errada no momento em que necessita de recolher dados do utilizador.

O teste prático é simples. Se o utilizador se destina a escrever, escolher ou clicar e pretender que o documento registe essa ação, necessita de um campo AcroForm. Se pretende deixar uma nota, delimitar uma área ou aplicar um carimbo de estado que acompanhe o ficheiro mas não constitua dados estruturados, utilize uma anotação. Misturar estes conceitos resulta em documentos com aspeto correto mas comportamento incorreto: um "campo" que ninguém consegue preencher ou um comentário que desaparece quando o formulário é limpo. O lado interativo, com tipos de campo, validações e ações de submissão, tem o seu próprio espaço abordado no guia de campos e ações AcroForm.

Compor uma página

Os vários elementos combinam-se da mesma forma que o restante ecossistema do HotPDF. Defina as propriedades do documento, chame o BeginDoc, desenhe o conteúdo de página necessário através das chamadas de texto e gráficos, adicione as anotações por cima e encerre com EndDoc. As anotações vinculam-se ao CurrentPage, pelo que, após um AddPage, serão inseridas na nova página, e uma nota destinada à página um surgirá silenciosamente na página dois se a adicionar após a quebra de página.

Pdf := THotPDF.Create(nil);
try
  Pdf.FileName := 'annotated.pdf';
  Pdf.Compression := cmFlateDecode;
  Pdf.FontEmbedding := True;
  Pdf.BeginDoc;

  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Pdf.CurrentPage.TextOut(50, 740, 0, 'Quarterly figures, draft for review');

  Pdf.CurrentPage.AddTextAnnotation(
    'Confirm the totals before sign-off.',
    Rect(50, 720, 70, 740), False, taComment, clBlue);
  Pdf.CurrentPage.AddFreeTextAnnotation(
    'DRAFT', Rect(450, 720, 540, 745), ftCenter, clRed);
  Pdf.CurrentPage.AddStampAnnotation(
    'For comment', Rect(50, 660, 180, 695), satForComment, clGreen);

  Pdf.EndDoc;
finally
  Pdf.Free;
end;

Um último reflexo útil a criar para quando o resultado não parece correto: abra o ficheiro em mais do que um visualizador antes de concluir que o código tem um erro. Carimbos e os ícones de notas menos comuns costumam ser a causa das diferenças e, como a anotação funciona como uma solicitação ao leitor e não como píxeis desenhados diretamente, a diferença de visualização entre o Acrobat e um leitor mais leve é muitas vezes a especificação a funcionar como previsto, e não uma falha na sua chamada.

As chamadas de anotação demonstradas aqui integram o HotPDF Component para Delphi e C++Builder.