Dois documentos abertos ao mesmo tempo, mesmo número de página, cada um em seu próprio painel de rolagem: esse é o núcleo de um visualizador de comparação. O PDFium Component oferece isso através de um modelo de objetos direto, onde TPdf possui o arquivo e TPdfView possui a exibição. Um documento, um TPdf, um TPdfView. Você quer três painéis, você tem três pares. As partes difíceis não são as chamadas de API; são a aritmética de layout quando a janela é redimensionada e a lógica de sincronização de página quando você decide qual visualização deve seguir qual
Layout do Formulário
O formulário VCL contém três contêineres TScrollBox lado a lado, cada um com um TPdfView dentro e alinhado a alClient para preencher a caixa. Dois componentes TSplitter ficam entre as caixas para que o usuário possa ajustar as larguras das colunas em tempo de execução. Uma barra de ferramentas acima dos painéis carrega os botões de abertura, controles de zoom e a alternância entre duas/três visualizações
O modo de três visualizações é um valor booleano que o formulário rastreia internamente. Quando ele é invertido, você recalcula as larguras e mostra ou oculta a terceira coluna. A abordagem mais simples é limpar todas as propriedades Align, ocultar os divisores e, em seguida, definir posições absolutas:
procedure TFormMain.UpdateLayout;
var
TotalWidth: Integer;
begin
TotalWidth := ClientWidth;
if ThreeViewMode then
begin
ScrollBox3.Visible := True;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 3;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth div 3;
ScrollBox3.Left := ScrollBox2.Left + ScrollBox2.Width;
ScrollBox3.Width := TotalWidth - ScrollBox3.Left;
// Aplique o mesmo (ClientHeight - altura da barra de ferramentas) aos três valores de Height
end
else
begin
ScrollBox3.Visible := False;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 2;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth - ScrollBox2.Left;
end;
end;
Definir Align := alNone nas três caixas antes da aritmética de inteiros evita que o mecanismo de restrição da VCL brigue com suas atribuições. Restaure a visibilidade do divisor após o posicionamento se quiser usar a ação de arrastar para redimensionar no modo de duas visualizações
A altura de cada caixa de rolagem é a área do cliente menos a altura do painel da barra de ferramentas. Como a barra de ferramentas está encaixada na parte superior com alTop, ClientHeight - PanelButtons.Height fornece o espaço vertical utilizável. Atribua isso às três caixas na mesma chamada a UpdateLayout para que nunca haja um quadro em que uma caixa seja mais alta que as outras, causando oscilação no layout
Abrindo um Documento
Cada par de painéis precisa do seu próprio procedimento de abertura. O padrão é curto: desative o componente, defina o nome do arquivo, ative-o e, em seguida, verifique Active; se permanecer False, solicite uma senha e tente novamente. Observe que TPdfView.Active é o que controla a renderização, mas TPdf.Active é o que realmente abre o arquivo; eles são independentes. Definir PdfView.Active := True quando o seu TPdf vinculado ainda não está ativo é inofensivo, mas não exibe nada
procedure TFormMain.OpenPdfFile(PdfComponent: TPdf;
PdfViewComponent: TPdfView);
var
Password: string;
begin
if not OpenDialog.Execute then
Exit;
PdfComponent.Active := False;
PdfComponent.FileName := OpenDialog.FileName;
PdfComponent.Password := '';
PdfComponent.Active := True;
// As falhas de carregamento são silenciosas: Active permanece False em vez de gerar exceção.
if not PdfComponent.Active then
begin
// Provavelmente um arquivo protegido por senha; dê ao usuário uma nova tentativa.
if InputQuery('Senha', 'Digite a senha do documento:', Password) then
begin
PdfComponent.Password := Password;
PdfComponent.Active := True;
end;
end;
if not PdfComponent.Active then
begin
ShowMessage('Não foi possível abrir ' + OpenDialog.FileName +
' (arquivo danificado ou senha incorreta)');
Exit;
end;
PdfViewComponent.PageNumber := 1;
SetActivePdfView(PdfViewComponent);
end;
Sempre verifique PdfComponent.Active após a atribuição; um arquivo danificado ou senha incorreta faz com que o carregamento falhe silenciosamente sem gerar uma exceção no caminho padrão. A definição explícita de PdfViewComponent.PageNumber := 1 após uma abertura bem-sucedida evita que o número de página antigo do documento anterior seja mantido
A caixa de diálogo de mensagem no final é intencional: você quer que arquivos corrompidos ou não suportados venham à tona imediatamente, em vez de serem engolidos como um painel em branco e silencioso. Um usuário que não vê nada não tem ideia se o arquivo foi carregado e está simplesmente vazio, ou se o componente o rejeitou. Relatar a falha mantém o erro visível
Rastreamento do Painel Ativo
Quando o usuário clica dentro de um painel, esse painel se torna ativo. O formulário rastreia um campo FActivePdfView: TPdfView privado. O feedback visual é uma alteração da cor da borda no contêiner TScrollBox: defina-a como clHighlight para a caixa ativa e clWindow para as outras. Conecte isso a cada TPdfView.OnClick e ao procedimento de abertura para que o foco acompanhe o documento que você acabou de abrir
Algumas operações se aplicam a todos os painéis visíveis em vez de apenas ao ativo. Um booleano FAllViewsMode no formulário direciona esse ramo. Quando é verdadeiro, as alterações de zoom e a navegação da página se propagam para cada painel que tenha um documento ativo:
procedure TFormMain.ApplyZoomToAll(NewZoom: Double);
begin
if PdfView1.Active then PdfView1.Zoom := NewZoom;
if PdfView2.Active then PdfView2.Zoom := NewZoom;
if ThreeViewMode and PdfView3.Active then PdfView3.Zoom := NewZoom;
end;
Navegação Sincronizada de Página
A navegação sincronizada é opcional, mas útil para fluxos de trabalho de revisão de documentos onde ambos os arquivos abrangem o mesmo intervalo de páginas. A lógica pertence a um manipulador de eventos que é acionado depois que o usuário navega em uma visualização. Quando uma visualização de origem altera seu PageNumber, o manipulador propaga esse número para as outras visualizações, sujeito a uma restrição: a visualização de destino deve ter pelo menos esse número de páginas, caso contrário, será ignorada
O PageNumber em TPdfView e em TPdf são independentes. TPdf.PageNumber rastreia qual página o componente de documento considera atual; TPdfView.PageNumber rastreia o que é exibido na tela. Para fins de navegação, você precisa da propriedade de visualização, não da propriedade de documento
Uma caixa de seleção com um rótulo semelhante a "Sincronizar páginas" dá o controle ao usuário. Quando está desmarcada, cada painel navega independentemente e o manipulador sai imediatamente. Essa independência é importante para casos de uso em que os dois documentos têm contagens de páginas diferentes ou quando o usuário deseja encontrar a passagem equivalente em uma tradução que começa em uma página diferente. Forçar sempre a sincronização tornaria a ferramenta mais difícil de usar do que uma simples organização de área de trabalho com duas janelas
Uma coisa a observar: a definição programática de PdfView.PageNumber dentro do manipulador de sincronização irá acionar o evento de alteração naquela visualização. Previna-se contra recursividade infinita com um sinalizador booleano que você define antes da atribuição e limpa logo em seguida. O sinalizador é por formulário e não por visualização, pois todas as três visualizações compartilham o mesmo manipulador
Zoom por Painel
Cada TPdfView possui sua própria propriedade Zoom, um Double em porcentagem onde Zoom := 100 significa o tamanho real (100%). A definição dela substitui qualquer FitMode ativo. Para um botão de ajustar à largura no painel ativo, leia o zoom de ajuste em PdfView.PageWidthZoom[PdfView.PageNumber] e atribua-o. Para ajustar à página, use PageZoom[PageNumber]. Ambas são propriedades de matriz indexadas por número de página baseado em 1, portanto, previna-se contra um número de página zero antes de acessá-las
Ao exportar a página atual para uma imagem, leia a rotação a partir da visualização, mas chame RenderPage no componente TPdf, não na visualização. A forma de bitmap de TPdf.RenderPage usa dimensões explícitas de pixel, além de um valor TRotation e um conjunto de TRenderOptions. A variante da função retorna um TBitmap de propriedade do chamador, que você mesmo libera após salvar:
procedure TFormMain.SaveActiveViewAsImage;
var
Pdf: TPdf;
Bmp: TBitmap;
Jpeg: TJpegImage;
begin
if not Assigned(FActivePdfView) or not FActivePdfView.Active then
Exit;
Pdf := FActivePdfView.Pdf;
Pdf.PageNumber := FActivePdfView.PageNumber;
Bmp := Pdf.RenderPage(
0, 0,
Round(Pdf.PageWidth * 2),
Round(Pdf.PageHeight * 2),
FActivePdfView.Rotation, [], clWhite);
try
if SavePictureDialog.Execute then
begin
Jpeg := TJpegImage.Create;
try
Jpeg.Assign(Bmp);
Jpeg.CompressionQuality := 90;
Jpeg.SaveToFile(SavePictureDialog.FileName);
finally
Jpeg.Free;
end;
end;
finally
Bmp.Free;
end;
end;
O multiplicador de 2x na largura e na altura fornece um resultado mais nítido para documentos com texto pequeno. O bloco try/finally em torno da liberação do bitmap não é opcional; o cancelamento de um TSaveDialog ainda atinge o bloco finally, e você deseja que o bitmap seja liberado, independentemente do que o usuário tenha feito
Requisitos da DLL
O PDFium Component encapsula a biblioteca nativa do PDFium. Um processo de host de 32 bits precisa de pdfium32.dll; um host de 64 bits precisa de pdfium64.dll. Variantes com o mecanismo V8 JavaScript adicionam o sufixo v8 e pesam aproximadamente de 23 a 27 MB, em comparação com as compilações padrão de 5 a 6 MB. Para um visualizador de comparação que desativa o preenchimento de formulários (Pdf.FormFill := False), a compilação padrão não V8 é suficiente e mantém a distribuição menor
Coloque a DLL no mesmo diretório que o executável, ou em qualquer diretório no PATH do sistema. O componente carrega-a sob demanda quando o primeiro TPdf é ativado, portanto, a falta de uma DLL aparece nesse momento em vez de na inicialização do aplicativo. Se você fornecer um instalador, a abordagem mais confiável é copiar a DLL para a pasta do aplicativo durante a instalação, em vez de depender de um diretório do sistema que um administrador pode limpar posteriormente
As compilações V8 são úteis principalmente quando você precisa interagir com ações JavaScript de PDF, por exemplo, para acionar campos de cálculo ou enviar manipuladores. Um visualizador de comparação passivo não tem motivos para executar JavaScript; definir Pdf.FormFill := False antes de Active := True pula completamente o ambiente de preenchimento de formulários, o que também significa que nenhum mecanismo JS é inicializado mesmo que a compilação padrão seja usada. Esse é o padrão correto para um visualizador somente leitura, independentemente da variante da DLL que você distribui
Para obter mais detalhes sobre o PDFium Component e sua API completa, visite a página do produto Delphi PDFium Component