Integrar un panel de lectura de PDF en una aplicación empresarial en Delphi no requiere escribir un bucle de renderizado, un motor de desplazamiento o un modelo de zoom. losLab PDF Library incluye TPDFlibViewer, un control VCL que abre un documento con una sola llamada y proporciona selección de texto, desplazamiento (panning), zoom de marquesina, capturas de pantalla (snapshots), resaltado de regiones, resaltado de búsquedas de texto completo, interacción con anotaciones e impresión como comportamiento integrado. Este artículo detalla los cinco pasos para convertir un formulario vacío en un visor funcional: arrastrar el control, conectar las acciones listas para usar, añadir el resaltado de búsqueda, gestionar los clics en las anotaciones e imprimir
¿Por qué usar un control de visor en lugar de escribir un bucle de renderizado?
El argumento de la estratificación por capas es lo primero, porque explica lo que realmente está adquiriendo. Renderizar una página en un mapa de bits es la parte fácil (un 20 por ciento); el otro 80 por ciento es todo lo que lo rodea: una caché de mapas de bits de página con un límite de desalojo, un diseño de desplazamiento a lo largo de páginas de diferentes tamaños, modos de zoom que se recalculan al cambiar el tamaño, pruebas de impacto (hit-testing) que asocian un píxel del ratón a coordenadas de página con cualquier zoom y rotación, y una programación de repintado que no parpadea. TPDFlibViewer se encarga de todo esto y delega el trabajo de rasterizado en el mismo motor descrito en el artículo sobre renderizado multimotor, de modo que la capa de control y la capa de renderizado siguen siendo reemplazables por separado. El código de su formulario se comunica con un control de Windows con desplazamiento, no con un renderizador
¿Cómo integrar un visor de PDF en una aplicación Delphi?
TPDFlibViewer.LoadFromFile abre un documento a través de la propia instancia interna TPDFlib del control y devuelve 1 si tiene éxito, por lo que el visor mínimo consiste en una llamada al constructor, una asignación de Parent y una carga. Si ya dispone de una instancia de TPDFlib en otra parte de la aplicación, el método AttachLibrary muestra el documento seleccionado en su lugar, manteniendo la propiedad de su lado
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; // desplazarse arrastrando; un clic normal sigue los enlaces
end;
La propiedad ViewerMode selecciona la acción del ratón entre cinco valores: vmSelect es la herramienta de selección de texto predeterminada; vmHand permite desplazarse arrastrando con un cursor de mano mientras que un clic normal sigue los enlaces; vmZoom es un zoom de marquesina que amplía la banda arrastrada hasta llenar el área de visualización; vmSnapshot captura una región arrastrada al portapapeles (con un evento OnSnapshot y un interruptor SnapshotToClipboard); y vmHighlight dibuja resaltados de región translúcidos persistentes. La herramienta de resaltado merece una mención sobre las coordenadas: las regiones se almacenan en el espacio de usuario de la página, no en píxeles del cliente, por lo que las marcas de AddHighlightRegion sobreviven al zoom, a la rotación de la vista y a la navegación de páginas sin necesidad de llevar registros adicionales por su parte. En una vista rotada, la dirección de arrastre incluso puede ir en contra de los ejes de la página, lo que genera un ancho o alto negativo tras el mapeo de rotación inverso; el control normaliza el rectángulo automáticamente
¿Cómo conectar una barra de herramientas de PDF en Delphi sin código de acoplamiento?
La unidad PDFlibViewerActions elimina por completo el código de acoplamiento de la barra de herramientas. Proporciona veinte descendientes de TAction listos para usar que cubren la navegación por páginas (TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), el zoom (TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), la rotación de la vista (TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), impresión, diálogos de abrir archivo y guardar como, copiar texto seleccionado y una acción de cambio por herramienta de ratón (TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode). Cada acción apunta a un control a través de su propiedad publicada Viewer y mantiene su propio estado: cada acción se desactiva cuando no hay ningún documento abierto, las acciones de navegación rastrean la posición de la página, la copia rastrea si existe una selección, y las acciones de modo y ajuste mantienen su estado de verificación sincronizado con la herramienta activa y el modo de zoom. En tiempo de diseño, todas se registran en la categoría losLab PDF del editor de Lista de acciones; en tiempo de ejecución, las mismas clases ocupan tres líneas cada una
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;
Existe una trampa a nivel de unidad que vale la pena conocer si construye su propio registro de acciones sobre este patrón. RegisterActions se declara en System.Actions, no en Vcl.ActnList, y el error del compilador que obtiene si solo incluye Vcl.ActnList en la cláusula uses es bastante engañoso: el literal de matriz de clases [TAction1, TAction2, ...] pasa a interpretarse como un conjunto (set), produciendo una cadena de errores E2010 Incompatible types: 'Integer' and 'class of ...' que parecen indicar un conflicto de tipos en sus clases de acción. La solución consiste en una única entrada en la cláusula uses, pero el diagnóstico apunta a cualquier parte menos a esa. Para barras de herramientas de zoom, TPDFlibViewer.EffectiveZoomPercent informa del zoom real en efecto (incluyendo el factor calculado en los modos de ajustar a ancho y ajustar a página), de modo que un paso de zoom de ampliación puede basarse en la ampliación real actual en lugar de en el último valor que usted asignó
Resaltado de resultados de búsqueda y la inversión de coordenadas que conlleva
HighlightSearchHits busca una consulta en todo el documento y dibuja una barra translúcida sobre cada coincidencia, devolviendo el número de resultados; ClearSearchHighlights, SearchHighlightCount y la propiedad SearchHighlightColor completan esta funcionalidad. Dado que los rectángulos de las coincidencias se almacenan en el espacio de la página y se vuelven a proyectar en cada repintado, las barras permanecen fijas sobre su texto mientras el usuario se desplaza, aplica zoom y rota la vista. Si utiliza el componente complementario TPDFlibSearchPanel, su método RunSearch dibuja automáticamente las mismas barras en el visor conectado a través de la propiedad HighlightInView, que está activada por defecto
procedure TMainForm.btnSearchClick(Sender: TObject);
var
Hits: Integer;
begin
FViewer.SearchHighlightColor := clYellow;
Hits := FViewer.HighlightSearchHits(edtQuery.Text, False); // insensible a mayúsculas/minúsculas
StatusBar1.SimpleText := Format('%d matches', [Hits]);
end;
La parte sutil, y la razón por la que es preferible utilizar la ruta integrada en lugar de implementarla usted mismo a partir de resultados de búsqueda brutos, es la conversión del sistema de coordenadas. El motor de texto informa de los rectángulos de coincidencia en coordenadas que dependen de la configuración Origin de la biblioteca: con un valor de Origin de 0 o 3, el eje Y se desplaza de abajo hacia arriba como en el espacio de usuario nativo de PDF, y con Origin en 2 o 3, el eje X se mide desde el borde derecho. La capa de superposición del visor funciona en un espacio de página basado en la parte superior izquierda, por lo que cada rectángulo de coincidencia debe invertirse (Y = PageHeight - Bottom y, cuando corresponda, X = PageWidth - Right) antes de almacenarse; tomar el borde superior informado al valor nominal bajo el origen predeterminado dibuja el resaltado en la posición reflejada verticalmente en la página. TPDFlibViewer realiza esta conversión de acuerdo con el origen activo cuando registra cada coincidencia, y luego aplica el mapeo de rotación directo en el momento del dibujo, que es la razón por la que las barras se sitúan correctamente en cualquier ángulo. La translucidez en sí misma utiliza la API AlphaBlend de Windows con un canal alfa de origen constante, ya que el lienzo VCL no dispone de composición alfa nativa. Las API subyacentes de búsqueda de texto y geometría se cubren en profundidad en el artículo sobre búsqueda de texto y enumeración de elementos de página
Clics en anotaciones, sugerencias y exportación de archivos adjuntos
TPDFlibViewer trata las anotaciones como objetos interactivos de primera clase. Al hacer clic en una anotación que no sea un enlace, se activa el evento OnAnnotClick con el número de página, el índice de anotación basado en 1 y la cadena del subtipo; AnnotAtPagePoint expone la misma prueba de impacto mediante programación y omite las anotaciones emergentes (Popup) complementarias para que siempre obtenga la anotación que el usuario ve realmente. Al pasar el cursor sobre una anotación, se muestra su texto Contents como la sugerencia del control a través del interruptor AnnotHints, que está activada por defecto y coopera con la propiedad estándar ShowHint de VCL. Para las anotaciones FileAttachment, SaveAnnotAttachmentToFile escribe el archivo incrustado en el disco y devuelve 1 si tiene éxito, lo que completa un flujo de trabajo de hacer clic para guardar en muy pocas líneas
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 := 'Archivo adjunto guardado';
end;
Impresión y los límites que debe prever
La impresión no requiere de una integración independiente. PrintDoc muestra el diálogo de impresión estándar de Windows (selección de impresora, rango de páginas, copias, ordenación) y entrega el trabajo al motor de impresión de la biblioteca, mientras que PrintDoc(False) envía todo el documento a la impresora predeterminada sin interfaz de usuario. PrintDocRange se realiza de forma totalmente programática con un nombre de impresora explícito, página inicial y final, recuento de copias y bandera de ordenación, lo que representa el punto de entrada correcto para trabajos de impresión por lotes o activados por servidor desde la misma instancia del visor
Dos límites merecen una mención honesta. En primer lugar, TPDFlibViewer es un control VCL de Windows; la capa del visor se excluye de las compilaciones NOVCL, por lo que los servicios de consola y los destinos que no sean VCL deben usar la API de renderizado subyacente de TPDFlib directamente. En segundo lugar, el uso sin interfaz gráfica (headless) se admite deliberadamente pero de forma limitada: CaptureRegion renderiza cualquier región del cliente (páginas más resaltados) en un mapa de bits propiedad del llamador y funciona en un control sin contenedor (unparented) y sin un manejador de ventana, debido a que la ruta de pintado evita las llamadas VCL que forzarían la creación del manejador. Esto hace práctica la verificación a nivel de píxel en pruebas automatizadas, pero cualquier cosa que realmente necesite un bucle de mensajes (como las herramientas interactivas del ratón) sigue perteneciendo a una sesión de interfaz de usuario real. En cuanto al rendimiento, el control mantiene una caché de páginas en memoria con un límite configurable mediante SetCacheLimit, y puede conservar páginas renderizadas entre sesiones; el artículo sobre la caché de páginas en disco del visor cubre esa capa y su comportamiento con DPI por monitor en detalle
Cinco pasos entonces: colocar el control y llamar a LoadFromFile, conectar las acciones listas para usar de PDFlibViewerActions, llamar a HighlightSearchHits para la búsqueda en la página, gestionar OnAnnotClick para flujos de trabajo de anotaciones y exponer PrintDoc. Cada elemento que se muestra aquí se distribuye en la losLab PDF Library estándar para Delphi, donde el control del visor, las clases de acción y el motor de renderizado se instalan de forma conjunta