Delphi y Lazarus compilan el mismo Object Pascal, y esa similitud superficial es exactamente lo que hace que portar un visor entre ellos sea engañoso. Las dos cadenas de herramientas divergen en tres lugares que importan para el trabajo con PDF: el tipo nativo string es UTF-16 en Delphi y UTF-8 en una aplicación LCL; VCL y LCL son marcos visuales diferentes con sus propios controles, cuadros de diálogo y formatos de flujo de formularios; y un binario de Delphi se dirige a Windows mientras que un binario de FPC puede estar destinado a Linux o macOS. Ninguna de esas diferencias se muestra en el momento de la compilación. Un visor creado sobre PDFium Component, que se distribuye en ediciones VCL y LCL desde un solo árbol de código fuente, compilará sin problemas en Lazarus después de un puñado de intercambios de nombres de unidades y unos pocos bloques {$IFDEF FPC}. Las fallas llegan más tarde, cuando los datos reales y una implementación real exponen las suposiciones que la compilación de Delphi estaba haciendo en silencio
Cuatro de esas suposiciones representan la mayor parte del tiempo perdido: la codificación de texto en el límite de la interfaz de usuario, la tentación de mantener dos copias del formulario, la forma en que el binario del motor nativo se resuelve en tiempo de ejecución, y el momento en que el texto a voz se queda sin plataforma una vez que SAPI ya no está. Cada una de ellas es barata de manejar si sabe que viene y costosa de rastrear si no lo sabe
Mismo Pascal, diferentes cargas útiles de cadenas
El string nativo de Delphi ha sido UTF-16 desde 2009. Lazarus y Free Pascal tienen UTF-8 por defecto en las aplicaciones LCL. Las API del componente orientadas a texto se comunican en UTF-16 a través del tipo WString, al cual la compilación de FPC le da un alias a WideString, por lo que cada límite donde el texto cruza entre su interfaz de usuario LCL y el motor PDF es un punto de conversión
Las conversiones ocurren automáticamente en asignaciones directas, y la mayoría del código nunca necesita pensar en ellas. Dos hábitos mantienen alejados los errores de codificación. Pase el texto directamente sin manipulación a nivel de bytes: el código que corta un término de búsqueda por desplazamiento de bytes funciona en Delphi, donde un Char es una unidad UTF-16, y corrompe los UTF-8 de múltiples bytes en la LCL. Y pruebe con datos que no sean ASCII desde la primera ejecución. Un nombre de archivo en alemán, un término de búsqueda en cirílico, un nombre de autor acentuado en los metadatos del documento: los datos de prueba puramente ASCII ocultan todos los defectos de codificación, porque ASCII es el único rango donde UTF-8 y UTF-16 coinciden byte por carácter. El error es real todo el tiempo; ASCII simplemente lo mantiene invisible hasta que un cliente en Múnich abre un archivo que usted nunca probó
Un bloque condicional, no una bifurcación por IDE
Después de la primera docena de IFDEFs, la base de código comienza a sentirse como dos proyectos vistiendo un solo repositorio, y bifurcarlo por IDE parece tentador. Es el movimiento equivocado. Las diferencias genuinas colapsan en un bloque de declaración compartido, y una bifurcación duplica el costo de cada corrección de errores a partir de ese momento. Mantenga la capa condicional así de pequeña:
{$IFDEF FPC}
uses
LCLType, Forms, Graphics, Controls;
type
WString = WideString; // las API de texto del componente son UTF-16
TBytes = array of Byte;
{$ELSE}
uses
Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}
Todo lo que está debajo de ese bloque se compila de forma idéntica en ambos IDE. Manejo de documentos, navegación de páginas, llamadas de renderizado: TPdf y TPdfView exponen la misma superficie en las ediciones VCL y LCL, por lo que la mayor parte del visor nunca ve una condición del compilador. Mantenerlo así es una disciplina estructural en lugar de un truco inteligente. La lógica PDF compartida vive en unidades que no extraen paneles ni cuadros de diálogo específicos de un marco. El puñado de cosas que genuinamente difieren, como los cuadros de diálogo de impresión y los selectores de archivos con las convenciones de su plataforma, se ocultan detrás de una interfaz delgada implementada una vez por marco. El bloque IFDEF se convierte en el único lugar donde se permite que aterrice la futura divergencia de la plataforma, en lugar de filtrar directivas del compilador a lo largo de cuarenta unidades
Construya el formulario en código, no en dos diseñadores
El flujo de formularios es donde los proyectos de dos IDE se pudren silenciosamente. Un .dfm y un .lfm que afirman describir el mismo formulario se desvían propiedad por propiedad hasta que las dos compilaciones se comportan de manera diferente por razones que nadie puede diferenciar, porque los dos archivos ni siquiera están en el mismo formato. Construir el visor en tiempo de ejecución elude todo el problema. Hay una secuencia constructora, controlada por versiones como código ordinario, y se lee igual en ambas plataformas:
procedure TViewerForm.FormCreate(Sender: TObject);
begin
Pdf := TPdf.Create(Self);
PdfView := TPdfView.Create(Self);
PdfView.Parent := Self;
PdfView.Align := alClient;
PdfView.Pdf := Pdf;
PdfView.FitMode := pfmFitWidth;
if ParamCount > 0 then
begin
Pdf.FileName := ParamStr(1);
Pdf.Active := True; // abre el documento; PageCount es válido después de esto
end;
end;
El orden exacto de esas asignaciones importa menos que la única línea que hace el trabajo real. PdfView.Pdf := Pdf vincula el control visual con el componente del documento, y desde ese punto la navegación de la página a través de PageNumber y el comportamiento de ajuste a través de FitMode responden de manera idéntica bajo VCL y LCL. Vale la pena conocer una peculiaridad entre marcos antes de que un usuario la reporte como un error: asignar el Zoom manualmente devuelve bruscamente FitMode a pfmNone en ambos marcos. Por lo tanto, si su barra de herramientas trata "ajustar al ancho" como una preferencia persistente, debe reasignar el modo de ajuste después de cualquier zoom programático, o la preferencia silenciosamente deja de persistir la primera vez que el código toca el nivel de zoom
El binario sobre el cual el IDE nunca le advirtió
El componente envuelve el motor de PDFium, que se distribuye como un binario de plataforma nativo, y ese binario es la fuente de casi todos los reportes de "funciona en el IDE, falla desde el acceso directo instalado". Tres reglas explican la mayoría de ellos. La arquitectura de bits tiene que coincidir exactamente. Un ejecutable de 32 bits no puede cargar una biblioteca de pdfium de 64 bits, y el mensaje que devuelve el sistema operativo ("no se encontró el módulo" en algunas versiones de Windows) engaña activamente, porque el archivo está justo ahí al lado del ejecutable. Resuelva la ruta de la biblioteca en relación con el ejecutable, nunca con el directorio de trabajo; un lanzamiento del IDE y un lanzamiento de la consola de comandos difieren precisamente en ese punto, que es la razón por la que el error se esconde durante el desarrollo. Y atrape una falla de carga antes de que se abra el primer documento, luego repórtela con la ruta y arquitectura esperadas especificadas. Un ticket de soporte que dice "Falta el binario de 64 bits de PDFium en <ruta>" se cierra en minutos. Uno que dice "el visor falla al inicio" se convierte en una semana de idas y vueltas
Versione el binario del motor junto al ejecutable mientras lo hace. PDFium se mueve rápido, y un instalador que actualiza la aplicación pero deja una biblioteca obsoleta en el disco produce caídas del sistema que nadie en su oficina puede reproducir, por la simple razón de que cada equipo en su oficina da la casualidad de que tiene el par que coincide. Trate la biblioteca como parte del artefacto de compilación, con el mismo instalador, el mismo sello de versión y la misma ruta de reversión que el ejecutable que la carga
Registro de componentes en el IDE de Lazarus
La construcción en tiempo de ejecución no necesita absolutamente ningún registro en tiempo de diseño, lo cual es la configuración más limpia para un visor que construye su propia interfaz de usuario en código. Cuando sí quiera los componentes en la paleta de Lazarus para el trabajo en tiempo de diseño, instale el paquete y deje que su unidad de registro dedicada, PDFiumLazReg en Lib/FPC/PDFiumLaz.lpk, lo maneje. Esa unidad está marcada como de tiempo de diseño a propósito: hace referencia a interfaces del editor de propiedades del IDE que nunca deben vincularse a su ejecutable de distribución
Haga esto mal y el síntoma es una aplicación que depende inexplicablemente de los paquetes del IDE, lo que aparece como un error de implementación en el primer equipo del cliente que nunca ha tenido Lazarus instalado
Texto a voz y lectores de pantalla fuera de Windows
La conversión de texto a voz es la única característica donde la historia multiplataforma se rompe, y se rompe en el sistema operativo, no en el componente. SAPI, el backend TTS habitual en Windows, existe solo en Windows. Una compilación de Lazarus que todavía tiene como objetivo Windows conserva la salida SAPI completa y el mismo comportamiento compatible con NVDA que tenía el original de Delphi, por lo que un port de Windows a Windows no pierde nada aquí, y un usuario de NVDA no puede diferenciar las dos compilaciones
Un objetivo Linux o macOS es un asunto diferente. No hay SAPI para llamar, por lo que la salida de audio tiene que reconfigurarse hacia un servicio de voz nativo mientras que las API de lectura que están por encima se quedan en su lugar. Esa separación es el argumento para poner la voz detrás de una interfaz desde el primer commit: el análisis de orden de lectura y el cursor de seguimiento de palabras son neutrales en cuanto a la plataforma y se trasladan intactos, y solo la capa delgada que realmente produce sonido tiene que cambiar por plataforma. El artículo del lector accesible cubre esa maquinaria de lectura a fondo
Una lista de verificación de paridad antes de dar por terminado el port
La siguiente pasada ha atrapado regresiones reales, listadas aproximadamente en el orden en que las fallas tienden a salir a la superficie. Abra un documento cuya ruta contenga caracteres no ASCII. Busque un término con caracteres no ASCII y confirme que las coincidencias se resalten donde deberían. Ejercite el desplazamiento de la rueda del mouse, la selección de arrastre y la navegación de páginas con el teclado en cada conjunto de widgets que envíe, porque el manejo del enfoque y el comportamiento de la rueda son los rincones de la LCL que más dependen del conjunto de widgets. Compruebe el renderizado a un escalado de pantalla del 100%, 150% y 200%. Por último, ejecute la compilación instalada, no la compilación del IDE, en un equipo que nunca haya tenido el IDE, porque esa es la única prueba que ejercita honestamente la resolución binaria. Todo lo demás puede pasar mientras eso falla en silencio
El rendimiento de renderizado se traslada entre las dos ediciones sin cambios, por lo que el enfoque de almacenamiento en caché del artículo de caché de renderizado y rendimiento de zoom se aplica al visor de LCL exactamente como está escrito para el de VCL
Nada de esto hace que la edición de LCL sea menor. La superficie central es idéntica en ambos lados: TPdf, TPdfView, renderizado, formularios, extracción de texto y las API de accesibilidad se comportan igual independientemente de qué IDE las compiló. Cada diferencia que vale la pena rastrear está vinculada a la plataforma en lugar de a la edición. La voz de SAPI es solo para Windows, los cuadros de diálogo siguen las convenciones de cada marco, y el binario tiene que coincidir con la arquitectura en la que se carga. Obtenga bien los límites de codificación, el formulario en tiempo de ejecución y la resolución binaria, y el resto del port es el trabajo mecánico que el compilador ya manejó por usted
Las ediciones VCL y LCL aquí descritas se envían juntas como PDFium Component, con código fuente y API públicas idénticas para Delphi, C++Builder y Lazarus/FPC