Artigo Técnico

Construir um Visualizador de PDF Interativo em Delphi com TPDFlibViewer

A integração de um painel de leitura de PDF numa aplicação comercial em Delphi não exige a escrita de ciclos de renderização, motores de deslocamento (scroll) ou lógicas de zoom. A biblioteca losLab PDF Library disponibiliza o TPDFlibViewer, um controlo VCL que abre um documento com uma única chamada, oferecendo funcionalidades nativas como seleção de texto, deslocação com a mão, zoom de seleção, capturas de ecrã (snapshots), realce de regiões e termos pesquisados, interação com anotações e impressões. Este artigo descreve os cinco passos para transformar um formulário vazio num visualizador funcional: inserir o controlo, associar as ações predefinidas, ativar o realce de pesquisas, gerir os cliques em anotações e configurar a impressão

Por que razão usar um controlo de visualização em vez de escrever um ciclo de renderização?

A separação de camadas é o argumento principal. Renderizar uma página num bitmap representa apenas 20% do trabalho; os restantes 80% envolvem a gestão periférica: cache de bitmaps com limites de memória, paginação de tamanhos distintos, reajuste do zoom ao redimensionar, conversão de coordenadas de ecrã para a escala e rotação da página nos cliques, e sincronização gráfica sem cintilação. O TPDFlibViewer gere estas tarefas e delega o desenho no motor abordado no artigo sobre renderização multimotor, mantendo a camada visual e o motor independentes. O seu formulário interage com um componente Windows com scroll e não com um renderizador gráfico

Como integrar um visualizador de PDF numa aplicação Delphi?

O método TPDFlibViewer.LoadFromFile carrega o documento através de uma instância interna de TPDFlib e devolve 1 em caso de sucesso. Assim, um visualizador básico requer apenas o construtor, a definição de Parent e a chamada de carregamento. Caso já possua uma instância de TPDFlib configurada na aplicação, AttachLibrary permite associá-la exibindo o documento respetivo:

uses PDFlibViewer;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FViewer := TPDFlibViewer.Create(Self);
  FViewer.Parent := Self;
  FViewer.Align := alClient;
  if FViewer.LoadFromFile('quarterly-report.pdf', '') = 1 then
    FViewer.ViewerMode := vmHand;  // deslocar arrastando; o clique simples continua a seguir links
end;

A propriedade ViewerMode define o comportamento do rato entre os cinco valores: vmSelect (seleção de texto padrão), vmHand (deslocação com cursor de mão mantendo o clique em hiperligações), vmZoom (zoom de seleção ampliando a área demarcada), vmSnapshot (copia a área selecionada para a área de transferência via evento OnSnapshot e switch SnapshotToClipboard) e vmHighlight (cria realces translúcidos persistentes). No caso dos realces, as coordenadas baseiam-se nas dimensões físicas da página (e não em píxeis do ecrã), pelo que os marcadores de AddHighlightRegion acompanham alterações de zoom, rotação e navegação sem intervenção manual. Em vistas rodadas, onde a direção de arrastamento pode inverter os eixos e gerar larguras ou alturas negativas após a inversão de rotação, o componente normaliza o retângulo automaticamente

Como configurar uma barra de ferramentas PDF em Delphi sem código de ligação?

A unidade PDFlibViewerActions elimina a necessidade de programar código de ligação para as barras de ferramentas. Disponibiliza vinte classes TAction predefinidas que abrangem navegação de páginas (TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), zoom (TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), rotação da vista (TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), impressão, diálogos de abertura e gravação de ficheiros, cópia de texto selecionado e seleção de modo do rato (TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode). Cada ação referencia o controlo pela propriedade Viewer e gere o seu estado interno: desativam-se se não houver documento aberto, controlam os limites de paginação, validam se existe texto selecionado para copiar e sincronizam o estado de seleção da ferramenta ativa. Em tempo de desenho, integram a categoria losLab PDF do Action List; em execução, as classes requerem apenas três linhas de configuração:

uses System.Actions, Vcl.ActnList, PDFlibViewer, PDFlibViewerActions;

procedure TMainForm.BuildToolbar;
var
  NextPage: TPDFlibViewerNextPage;
  FitWidth: TPDFlibViewerFitWidth;
  HandMode: TPDFlibViewerHandMode;
begin
  NextPage := TPDFlibViewerNextPage.Create(Self);
  NextPage.ActionList := ActionList1;
  NextPage.Viewer := FViewer;
  btnNext.Action := NextPage;

  FitWidth := TPDFlibViewerFitWidth.Create(Self);
  FitWidth.ActionList := ActionList1;
  FitWidth.Viewer := FViewer;
  btnFitWidth.Action := FitWidth;

  HandMode := TPDFlibViewerHandMode.Create(Self);
  HandMode.ActionList := ActionList1;
  HandMode.Viewer := FViewer;
  btnHand.Action := HandMode;
end;

Atenção à unidade de registo das ações: o método RegisterActions pertence a System.Actions e não a Vcl.ActnList. Se omitir a primeira, o compilador gera erros enganadores: a declaração do array de classes [TAction1, TAction2, ...] é interpretada como um conjunto (set), resultando em mensagens do tipo E2010 Incompatible types: 'Integer' and 'class of ...' sugerindo incompatibilidades nas suas classes. Para barras de zoom, a propriedade TPDFlibViewer.EffectiveZoomPercent devolve a percentagem real em vigor — incluindo as escalas calculadas para largura e altura de página —, permitindo aplicar aumentos sucessivos com base na dimensão atual

Realce de pesquisas e a inversão de coordenadas associada

O método HighlightSearchHits pesquisa os termos em todo o documento, desenha retângulos translúcidos sobre as ocorrências e devolve a contagem obtida; o controlo disponibiliza também ClearSearchHighlights, SearchHighlightCount e a propriedade SearchHighlightColor. Como os retângulos são registados no espaço de coordenadas da página e recalculados a cada desenho, os realces acompanham o texto em operações de scroll, zoom e rotação. Se utilizar o componente complementar TPDFlibSearchPanel, a chamada RunSearch projeta os realces no visualizador associado via propriedade HighlightInView, ativa por padrão:

procedure TMainForm.btnSearchClick(Sender: TObject);
var
  Hits: Integer;
begin
  FViewer.SearchHighlightColor := clYellow;
  Hits := FViewer.HighlightSearchHits(edtQuery.Text, False);  // ignorar maiúsculas/minúsculas
  StatusBar1.SimpleText := Format('%d matches', [Hits]);
end;

A complexidade da conversão de coordenadas justifica a preferência pela rotina nativa. O motor de texto reporta os retângulos das correspondências de acordo com a propriedade Origin da biblioteca: nas opções 0 ou 3, o eixo Y cresce de baixo para cima (como no espaço de utilizador nativo do PDF), e nas opções 2 ou 3 o eixo X é medido a partir da margem direita. Por sua vez, os realces do visualizador baseiam-se em coordenadas com origem no canto superior esquerdo da página, exigindo inverter as coordenadas (Y = PageHeight - Bottom e, se aplicável, X = PageWidth - Right) antes de as registar. Usar os valores diretos com a origem padrão desenharia o realce numa posição espelhada verticalmente na página. O TPDFlibViewer trata esta conversão em função do valor ativo de Origin e aplica a transformação de rotação correspondente no desenho. A translucidez recorre à API AlphaBlend do Windows, uma vez que a tela (canvas) da VCL não suporta composição alfa nativa. Funções adicionais de geometria e pesquisa de texto são detalhadas no artigo sobre pesquisa de texto e enumeração de elementos

Interação com anotações, dicas e exportação de anexos

O TPDFlibViewer suporta a interação com anotações de forma nativa. O clique numa anotação (que não seja um link) despoleta o evento OnAnnotClick com o número da página, o índice baseado em 1 e o tipo de anotação; AnnotAtPagePoint efetua a mesma validação programaticamente, ignorando anotações secundárias (Popup) para identificar a que está visível. Passar o rato sobre a anotação exibe o texto sob a forma de dica (hint) utilizando a propriedade AnnotHints (cooperando com a propriedade padrão ShowHint da VCL). Nas anotações de anexo (FileAttachment), SaveAnnotAttachmentToFile extrai o ficheiro para o disco devolvendo 1 em caso de sucesso, facilitando fluxos de exportação:

procedure TMainForm.ViewerAnnotClick(Sender: TObject; APage, AnnotIndex: Integer;
  const Subtype: WideString);
begin
  if Subtype = 'FileAttachment' then
    if FViewer.SaveAnnotAttachmentToFile(APage, AnnotIndex,
        'C:\Temp\attached-invoice.xml') = 1 then
      StatusBar1.SimpleText := 'Anexo gravado com sucesso';
end;

Impressão e limites da biblioteca

O suporte de impressão é direto. O método PrintDoc exibe o diálogo padrão do Windows (seleção de impressora, intervalo, cópias, agrupamento) e encaminha o processo para o motor de impressão; PrintDoc(False) envia o documento completo para a impressora padrão de forma silenciosa. A chamada PrintDocRange permite controlar estes parâmetros via código (definindo a impressora, páginas inicial e final, número de cópias e agrupamento), sendo indicada para tarefas automatizadas em servidores

Limitações técnicas relevantes: em primeiro lugar, o TPDFlibViewer é um componente VCL para Windows, estando excluído de compilações sem VCL (NOVCL) onde deve ser utilizada diretamente a API de desenho de TPDFlib; em segundo lugar, o processamento sem ecrã (headless) é suportado de forma condicionada: CaptureRegion desenha áreas (incluindo realces) num bitmap fornecido pela aplicação, funcionando em componentes criados sem atribuição de Parent uma vez que as rotinas de desenho evitam criar identificadores (handles) de janelas na VCL. Isto permite testes automatizados de píxeis, embora interações com rato que exijam ciclos de mensagens necessitem de uma interface ativa. Em termos de desempenho, a cache de memória é configurada por SetCacheLimit e suporta cache persistente em disco (abordada no artigo sobre cache em disco e DPI por monitor)

Passos de implementação: insira o componente, invoque LoadFromFile, associe as ações de PDFlibViewerActions, execute HighlightSearchHits para pesquisas de termos e trate os eventos em OnAnnotClick e PrintDoc. Todas estas classes integram a versão padrão do losLab PDF Library para Delphi