Dos documentos abiertos a la vez, con el mismo número de página, cada uno en su propio panel desplazable: ese es el núcleo de un visor de comparación. PDFium Component lo entrega mediante un modelo de objetos directo en el que TPdf posee el archivo y TPdfView posee la visualización. Un documento, un TPdf, un TPdfView. Si quiere tres paneles, tiene tres pares. Las partes difíciles no son las llamadas a la API; son la aritmética de disposición cuando la ventana cambia de tamaño y la lógica de sincronización de páginas cuando decide qué vista debe seguir a cuál
Disposición del formulario
El formulario VCL contiene tres contenedores TScrollBox uno al lado del otro, cada uno con un TPdfView dentro y alineado con alClient para que llene la caja. Dos componentes TSplitter se ubican entre las cajas para que el usuario pueda ajustar el ancho de las columnas en tiempo de ejecución. Una barra de herramientas sobre los paneles lleva los botones de apertura, los controles de zoom y el conmutador de dos vistas / tres vistas
El modo de tres vistas es un booleano que el formulario mantiene internamente. Cuando cambia, usted recalcula los anchos y muestra u oculta la tercera columna. El enfoque más simple es limpiar todas las propiedades Align, ocultar los divisores y luego fijar posiciones 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 el mismo (ClientHeight - alto de la barra) a los tres valores 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;
Fijar Align := alNone en las tres cajas antes de la aritmética entera evita que el motor de restricciones de la VCL pelee contra sus asignaciones. Restaure la visibilidad de los divisores después de posicionar si quiere el redimensionamiento por arrastre en el modo de dos vistas
El alto de cada caja de desplazamiento es el área de cliente menos el alto del panel de la barra de herramientas. Como la barra está acoplada arriba con alTop, ClientHeight - PanelButtons.Height le da el espacio vertical utilizable. Asigne esto a las tres cajas dentro de la misma llamada a UpdateLayout, para que nunca haya un cuadro en el que una caja sea más alta que las otras y provoque un parpadeo de disposición
Abrir un documento
Cada par de paneles necesita su propio procedimiento de apertura. El patrón es corto: desactive el componente, fije el nombre de archivo, actívelo y luego compruebe Active; si quedó en False, pida una contraseña y reintente. Tenga en cuenta que TPdfView.Active es lo que controla el renderizado, pero TPdf.Active es lo que realmente abre el archivo; son independientes. Fijar PdfView.Active := True cuando su TPdf vinculado todavía no está activo es inofensivo, pero no muestra 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;
// Las fallas de carga son silenciosas: Active queda en False sin lanzar.
if not PdfComponent.Active then
begin
// Lo más probable es un archivo con contraseña; dé un reintento al usuario.
if InputQuery('Password', 'Enter document password:', Password) then
begin
PdfComponent.Password := Password;
PdfComponent.Active := True;
end;
end;
if not PdfComponent.Active then
begin
ShowMessage('Could not open ' + OpenDialog.FileName +
' (damaged file or wrong password)');
Exit;
end;
PdfViewComponent.PageNumber := 1;
SetActivePdfView(PdfViewComponent);
end;
Compruebe siempre PdfComponent.Active después de la asignación; un archivo dañado o una contraseña equivocada hacen que la carga falle en silencio, sin lanzar una excepción en la ruta predeterminada. Fijar PdfViewComponent.PageNumber := 1 de forma explícita tras una apertura exitosa evita arrastrar un número de página viejo del documento anterior
El diálogo de mensaje del final es intencional: usted quiere que los archivos corruptos o no admitidos salgan a la superficie de inmediato en lugar de quedar tragados como un panel en blanco silencioso. Un usuario que no ve nada no tiene idea de si el archivo se cargó y simplemente está vacío, o si el componente lo rechazó. Informar la falla mantiene visible el error
Seguimiento del panel activo
Cuando el usuario hace clic dentro de un panel, ese panel pasa a ser el activo. El formulario mantiene un campo privado FActivePdfView: TPdfView. La retroalimentación visual es un cambio de color de borde en el TScrollBox contenedor: fíjelo en clHighlight para el activo y en clWindow para los demás. Conecte esto a cada TPdfView.OnClick y al procedimiento de apertura, para que el foco siga al documento que acaba de abrir
Algunas operaciones se aplican a todos los paneles visibles y no solo al activo. Un booleano FAllViewsMode en el formulario gobierna esa rama. Cuando es verdadero, los cambios de zoom y la navegación de páginas se abren en abanico hacia cada panel que tenga un documento activo:
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;
Navegación de páginas sincronizada
La navegación sincronizada es opcional, pero útil en flujos de revisión de documentos donde ambos archivos cubren el mismo rango de páginas. La lógica pertenece a un manejador de eventos que se dispara después de que el usuario navega en una vista. Cuando una vista fuente cambia su PageNumber, el manejador propaga ese número a las demás vistas, sujeto a una salvaguarda: la vista de destino debe tener al menos esa cantidad de páginas, de lo contrario se omite
El PageNumber de TPdfView y el de TPdf son independientes. TPdf.PageNumber registra qué página considera actual el componente de documento; TPdfView.PageNumber registra lo que se muestra en pantalla. Para fines de navegación conviene la propiedad de la vista, no la del documento
Una casilla de verificación rotulada algo así como "Sync pages" le da control al usuario. Cuando está desmarcada, cada panel navega de forma independiente y el manejador sale de inmediato. Esa independencia importa en los casos donde los dos documentos tienen distinta cantidad de páginas, o donde el usuario quiere encontrar el pasaje equivalente en una traducción que empieza en otra página. Forzar siempre la sincronización volvería la herramienta más difícil de usar que un simple arreglo de dos ventanas en el escritorio
Un detalle para vigilar: fijar PdfView.PageNumber por programa dentro del manejador de sincronización dispara a su vez el evento de cambio de esa vista. Protéjase contra la recursión infinita con un indicador booleano que fije antes de la asignación y limpie inmediatamente después. El indicador es por formulario, no por vista, porque las tres vistas comparten el mismo manejador
Zoom por panel
Cada TPdfView lleva su propia propiedad Zoom, un Double en porcentaje donde Zoom := 100 significa tamaño real (100%). Fijarlo anula cualquier FitMode activo. Para un botón de ajustar al ancho en el panel activo, lea el zoom de ajuste desde PdfView.PageWidthZoom[PdfView.PageNumber] y asígnelo. Para ajustar a la página, use PageZoom[PageNumber]. Ambas son propiedades de arreglo indexadas por número de página basado en 1, así que protéjase contra un número de página cero antes de acceder a ellas
Cuando exporte la página actual a una imagen, lea la rotación desde la vista pero llame a RenderPage en el componente TPdf, no en la vista. La forma de mapa de bits de TPdf.RenderPage toma dimensiones explícitas en píxeles más un valor TRotation y un conjunto TRenderOptions. La variante de función devuelve un TBitmap cuya propiedad es del llamador y que usted libera por su cuenta después de guardar:
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;
El multiplicador 2x sobre el ancho y el alto da una salida más nítida para documentos con texto fino. El try/finally alrededor de la liberación del mapa de bits no es opcional; una cancelación de TSaveDialog igual pasa por el bloque finally, y usted quiere que el mapa de bits se libere sin importar lo que haya hecho el usuario
Requisitos de la DLL
PDFium Component envuelve la biblioteca nativa pdfium. Un proceso anfitrión de 32 bits necesita pdfium32.dll; uno de 64 bits necesita pdfium64.dll. Las variantes con el motor JavaScript V8 agregan el sufijo v8 y pesan aproximadamente entre 23 y 27 MB frente a los 5-6 MB de las compilaciones estándar. Para un visor de comparación que deshabilita el llenado de formularios (Pdf.FormFill := False), la compilación estándar sin V8 alcanza y mantiene la distribución más pequeña
Coloque la DLL en el mismo directorio que el ejecutable, o en cualquier directorio del PATH del sistema. El componente la carga bajo demanda cuando se activa el primer TPdf, así que una DLL ausente sale a la luz en ese momento y no al iniciar la aplicación. Si distribuye un instalador, el enfoque más confiable es copiar la DLL a la carpeta de la aplicación durante la instalación en lugar de depender de un directorio del sistema que un administrador quizá limpie más adelante
Las compilaciones con V8 son útiles sobre todo cuando necesita interactuar con acciones JavaScript de PDF, por ejemplo para disparar campos de cálculo o manejadores de envío. Un visor de comparación pasivo no tiene ninguna razón para ejecutar JavaScript; fijar Pdf.FormFill := False antes de Active := True se salta por completo el entorno de llenado de formularios, lo que además significa que no se inicializa ningún motor JS aunque se use la compilación estándar. Ese es el valor predeterminado correcto para un visor de solo lectura, sea cual sea la variante de DLL que distribuya
Para más detalles sobre PDFium Component y su API completa, visite la página de producto de Delphi PDFium Component