Un visor de PDF en Delphi se reduce a dos componentes y el cableado entre ellos. TPdf posee el documento: abre el fichero, lo descifra y responde preguntas sobre el recuento de páginas y los metadatos. TPdfView es el control visual que pinta las páginas en pantalla y maneja el desplazamiento, el zoom y la página que el usuario está mirando. PDFium Component envuelve el mismo motor de renderizado que se distribuye dentro de Chrome, de modo que los glifos, el antialiasing y el color que obtiene en el lienzo coinciden con lo que sus usuarios ya ven en su navegador. El trabajo no está en el renderizado. Está en conectar el objeto de documento a la vista, cargar sin crash un fichero dañado o protegido con contraseña, y dar al usuario el puñado de controles que hacen que un visor se sienta acabado: pasar la página, cambiar el zoom, ajustar la página a la ventana
Esto recorre ese ensamblaje en el orden en que realmente lo construye. Todo lo de aquí renderiza una sola página a la vez, que es lo que quiere la mayoría de los flujos de trabajo de documentos. Si necesita páginas apiladas en una columna con desplazamiento continuo, esa es una decisión de maquetación distinta y no el camino de aquí
Conectar TPdf a TPdfView
Suelte un TPdf y un TPdfView en el formulario, y luego dígale a la vista qué documento mostrar. Esa única asignación es todo el enlace entre el documento no visual y el control que lo pinta
procedure TFormMain.FormCreate(Sender: TObject);
begin
// Pdf y PdfView se colocaron en tiempo de diseño.
PdfView.Pdf := Pdf; // la vista pinta lo que contiene este documento
PdfView.FitMode := pfmFitWidth; // comienza al usuario en un zoom razonable
end;
Antes de que nada de esto se ejecute, la biblioteca nativa PDFium tiene que estar en la máquina. PDFium Component carga pdfium.dll tanto en Win32 como en Win64; las dos compilaciones comparten ese nombre de fichero y se distribuyen en carpetas separadas DLLs\Win32 y DLLs\Win64, y el documento simplemente se niega a abrirse si la DLL no se puede encontrar o su bitness no coincide con el de su ejecutable. Distribuya la DLL que corresponda a su plataforma de destino junto a su ejecutable, o colóquela donde el cargador del sistema la encuentre. Las compilaciones con V8 existen solo para los PDF que llevan JavaScript que usted quiera ejecutar, cosa que un visor normal no hace, así que alcance la DLL estándar a menos que tenga una razón concreta para no hacerlo
Cargar un documento sin confiar en la entrada
El instinto es envolver la carga en un try/except y tratar una excepción lanzada como un fallo. Ese instinto está mal aquí, y equivocarse produce un visor que se ve bien hasta que alguien le entrega un fichero roto. Poner Active := True no lanza excepción en un fallo de carga. PDFium Component atrapa el error interno y deja Active en False, así que la única manera honesta de saber si el documento se abrió es leer la propiedad de vuelta después de asignarla
procedure TFormMain.OpenDocument(const FileName: string);
begin
Pdf.FileName := FileName;
Pdf.Active := True; // nunca lanza excepción; el fallo deja Active = False
if not Pdf.Active then
begin
ShowMessage('Could not open ' + FileName);
Exit;
end;
PdfView.PageNumber := 1; // la vista sigue su propia página actual
PdfView.Active := True; // hasta que la vista está activa no sostiene ninguna página
UpdatePageLabel;
end;
La vista tiene su propio interruptor Active, y hasta que no es True el control no sostiene ninguna página: abrir el documento y asignar PageNumber a secas deja el visor en blanco. Dos cosas más merecen atención. La primera es que PageNumber existe en ambos objetos y los dos son independientes. Pdf.PageNumber es la noción del documento de una página actual; PdfView.PageNumber es la página que el control realmente muestra, y es la que usted ajusta para mover al usuario por el fichero. Asignar una no mueve la otra, de manera que un visor siempre conduce la propiedad de la vista. La segunda es la indexación en base 1: las páginas van desde 1 hasta Pdf.PageCount, no desde 0, lo cual pilla desprevenido a todo aquel que esté acostumbrado a los arrays en base cero
Manejar un fichero encriptado
Los documentos cifrados se integran en la misma ruta de carga. Si la contraseña de apertura se asigna antes de la activación, el documento se descifra al abrirse; si es incorrecta o falta, Active se queda en False exactamente igual que con un fichero corrupto. Así que la recuperación es pedir una contraseña e intentar la activación otra vez
procedure TFormMain.OpenWithPassword(const FileName: string);
var
Password: string;
begin
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
begin
if InputQuery('Password required', 'Password:', Password) then
begin
Pdf.Password := Password; // debe asignarse antes de Active := True
Pdf.Active := True;
end;
if not Pdf.Active then
begin
ShowMessage('Unable to open the document.');
Exit;
end;
end;
PdfView.PageNumber := 1;
PdfView.Active := True;
end;
Como el fallo es silencioso tanto para una contraseña incorrecta como para un fichero dañado, no se pueden distinguir los dos a partir de Active solo. En la práctica eso es aceptable para un visor: el usuario o bien aporta la contraseña correcta o bien se entera de que el fichero no se abrirá, y el mensaje se lee igual de ambas formas
Navegar por el documento
Con el documento abierto, la navegación es aritmética sobre PdfView.PageNumber delimitada por Pdf.PageCount. El único trabajo real es el pinzamiento, de modo que los botones nunca empujen la página fuera del rango y los botones del principio y del final permanezcan deshabilitados en los extremos del fichero
procedure TFormMain.GoToPage(NewPage: Integer);
begin
if not Pdf.Active then
Exit;
if NewPage < 1 then
NewPage := 1
else if NewPage > Pdf.PageCount then
NewPage := Pdf.PageCount;
PdfView.PageNumber := NewPage;
UpdatePageLabel;
end;
// los cuatro botones de navegación se reducen a una llamada cada uno
procedure TFormMain.FirstClick(Sender: TObject); begin GoToPage(1); end;
procedure TFormMain.PrevClick(Sender: TObject); begin GoToPage(PdfView.PageNumber - 1); end;
procedure TFormMain.NextClick(Sender: TObject); begin GoToPage(PdfView.PageNumber + 1); end;
procedure TFormMain.LastClick(Sender: TObject); begin GoToPage(Pdf.PageCount); end;
Un cuadro de texto de "ir a la página N" es la misma llamada a GoToPage alimentada por un entero analizado, y la sujeción cubre el caso donde el usuario teclea 9999 en un fichero de diez páginas. Mantenga UpdatePageLabel como el único lugar que escribe "Página 3 de 12" de modo que la lectura nunca se desvíe en sincronía de lo que la vista muestra
Zoom: porcentajes explícitos y modos de ajuste
El zoom en TPdfView llega en dos sabores que interactúan, y comprender la interacción es la diferencia entre un control de zoom que se comporta y uno que se pelea con el usuario. La ruta directa es la propiedad Zoom, un porcentaje donde 100 significa el tamaño real. La otra ruta es FitMode, la cual le indica a la vista que calcule el zoom por usted y que lo mantenga recalculado mientras se cambia el tamaño de la ventana
// magnificaciones fijas
PdfView.Zoom := 100; // tamaño real
PdfView.Zoom := 50; // la mitad
PdfView.Zoom := 200; // doble
// deja que la vista dimensione la página a la ventana y la mantenga dimensionada al redimensionar
PdfView.FitMode := pfmFitWidth; // el ancho de la página llena el control
PdfView.FitMode := pfmFitPage; // toda la página visible
PdfView.FitMode := pfmActualSize; // 1:1 con los puntos del documento
Aquí está la parte con la que la gente tropieza. Asignar Zoom directamente restablece FitMode a pfmNone. Ese es el comportamiento correcto, no un error: en el momento en que el usuario escoge un 150% exacto, la vista ya no puede a la vez honrar el "ajustar a la anchura", porque ambas peticiones entran en conflicto. La consecuencia para su UI es que un botón de ampliar y un botón de ajustar a la página son estados mutuamente excluyentes, y la barra de herramientas debería hacer visible el modo activo. Cuando el usuario hace clic en ajustar a la página, fije FitMode; cuando haga clic en un zoom numérico, fije Zoom y deje que elimine el modo de ajuste por su cuenta
Si prefiere calcular el valor de ajuste usted mismo, quizás para alimentar un slider de zoom con el porcentaje de ajuste actual, los helpers por página le dan los números sin cambiar el modo. PageWidthZoom[N], PageZoom[N] y ActualSizeZoom[N] devuelven el porcentaje que ajustaría la página N al ancho, la ajustaría completa o la renderizaría a tamaño real
// siembra una lectura del zoom a partir del valor de ajuste a la anchura de la página actual
var
FitPercent: Double;
begin
FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;
Lo que realmente necesita un visor terminado
El visor anterior consta de unas pocas decenas de líneas, y ya hace el trabajo que necesita un flujo de trabajo de documentos: abre un fichero, sobrevive a uno malo, muestra una página, se mueve entre páginas y cambia la ampliación a mano o por ajuste. PDFium hace las partes difíciles silenciosamente. Las fuentes incrustadas se resuelven, las anotaciones y los campos de formulario se pintan allí donde el documento los coloca, y la página que usted ve concuerda con la que vería un usuario de Chrome, porque es el mismo motor el que dibuja ambas
A partir de esta base las incorporaciones son incrementales antes que estructurales. La selección de texto y la búsqueda leen desde la misma capa de texto que PDFium ya construye; metadatos como Pdf.Title y Pdf.Author están a una lectura de propiedad de distancia; la rotación y la escala de grises son opciones de renderizado que usted pasa cuando dibuja una página a un mapa de bits. Ninguno de estos altera la espina dorsal que tiene aquí, que es el objeto documento, la vista, y el flujo de cargar-luego-navegar que los conecta. Entienda bien esa espina dorsal y el resto es decoración
Los componentes TPdf y TPdfView utilizados en todo esto son parte de PDFium Component para Delphi y C++Builder, el cual contiene la referencia completa del visor en su página de producto